Deserializing oData to a sane object with ServiceStack - servicestack

So here's what I'm getting back from the oData service...
{
"odata.metadata":"http://server.ca/Mediasite/Api/v1/$metadata#UserProfiles",
"value":[
{
"odata.id":"http://server.ca/Mediasite/Api/v1/UserProfiles('111111111111111')",
"QuotaPolicy#odata.navigationLinkUrl":"http://server.ca/Mediasite/Api/v1/UserProfiles('111111111111111')/QuotaPolicy",
"#SetQuotaPolicyFromLevel":{
"target":"http://server.ca/Mediasite/Api/v1/UserProfiles('111111111111111')/SetQuotaPolicyFromLevel"
},
"Id":"111111111111111",
"UserName":"testuser",
"DisplayName":"testuser Large",
"Email":"testuser#testuser.ca",
"Activated":true,
"HomeFolderId":"312dcf4890df4b129e248a0c9a57869714",
"ModeratorEmail":"testuser#testuserlarge.ca",
"ModeratorEmailOptOut":false,
"DisablePresentationContentCompleteEmails":false,
"DisablePresentationContentFailedEmails":false,
"DisablePresentationChangeOwnerEmails":false,
"TimeZone":26,
"PresenterFirstName":null,
"PresenterMiddleName":null,
"PresenterLastName":null,
"PresenterEmail":null,
"PresenterPrefix":null,
"PresenterSuffix":null,
"PresenterAdditionalInfo":null,
"PresenterBio":null,
"TrustDirectoryEntry":null
}
]
}
I want to deserialize this into a simple class, like just the important stuff (Id, Username, etc...to the end).
I have my class create, but for the life of me I can't figureout how to throw away all the wrapper objects oData puts around this thing.
Can anyone shed some light?

You can use JsonObject do dynamically traverse the JSON, e.g:
var users = JsonObject.Parse(json).ArrayObjects("value")
.Map(x => new User
{
Id = x.Get<long>("Id"),
UserName = x["UserName"],
DisplayName = x["DisplayName"],
Email = x["Email"],
Activated = x.Get<bool>("Activated"),
});
users.PrintDump();
Or deserialize it into a model that matches the shape of the JSON, e.g:
public class ODataUser
{
public List<User> Value { get; set; }
}
public class User
{
public long Id { get; set; }
public string UserName { get; set; }
public string DisplayName { get; set; }
public string Email { get; set; }
public bool Activated { get; set; }
public string HomeFolderId { get; set; }
public string ModeratorEmail { get; set; }
public bool ModeratorEmailOptOut { get; set; }
public bool DisablePresentationContentCompleteEmails { get; set; }
public bool DisablePresentationContentFailedEmails { get; set; }
public bool DisablePresentationChangeOwnerEmails { get; set; }
public int TimeZone { get; set; }
}
var odata = json.FromJson<ODataUser>();
var user = odata.Value[0];

Related

EF Core with AutoMapper

I have a DTO containing properties and a Model, eg student can have more than one module and a module can be associated with more than one student. the properties are mapping fine but the Model doesn't map.
public class GetStudentByIdMapping : Profile
{
public GetStudentByIdMapping()
{
CreateMap<Student,StudentDetails>();
CreateMap<Module, StudentDetails>()
.ForPath(dest => dest.StudentModules.ModuleName, opt => opt.MapFrom(m => m.ModuleName))
.ForPath(dest => dest.StudentModules.ModuleCode, opt => opt.MapFrom(m => m.ModuleCode))
.ForPath(dest => dest.StudentModules.Description, opt => opt.MapFrom(m => m.Description))
.ReverseMap();
}
}
public async Task<StudentDetails> GetStudent(int studentId)
{
var student = context.Student
.Where(s => s.StudentId == studentId)
.FirstOrDefault();
var module = await context.Order
.Include(m => m.Module)
.Where(o => o.StudentId == studentId)
.Select(m => m.Module).ToListAsync();
var studMap = Mapper.Map<StudentDetails>(student);
Mapper.Map<StudentDetails>(module);
return studMap;
}
These are the ViewModels I want to map to the Models Model in the StudentDetails ViewModel
public class StudentDetails
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public StudentModule StudentModules { get; set; }
}
public class StudentModule
{
public string ModuleName { get; set; }
public string ModuleCode { get; set; }
public string Description { get; set; }
}
These are my Entities generated by EF Core
public partial class Student
{
public Student()
{
Order = new HashSet<Order>();
}
public int StudentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public virtual ICollection<Order> Order { get; set; }
}
public partial class Module
{
public Module()
{
Order = new HashSet<Order>();
}
public int ModuleId { get; set; }
public int? LectureId { get; set; }
public string ModuleName { get; set; }
public string ModuleCode { get; set; }
public string Description { get; set; }
public string ModulePath { get; set; }
public virtual Lecture Lecture { get; set; }
public virtual ICollection<Order> Order { get; set; }
}
You only need to pass the created destination to the second map call:
Just try the following code when mapping:
var studMap = Mapper.Map<Student,StudentDetails>(student);
Mapper.Map<Module,StudentDetails>(module,studMap);
Then the studMap will receive all mapped fields value.

Error 400.0 when returning DTO in Controller and Azure Mobile Client Sync Table

Since the object we need in the Mobile Client needs to access its related/associated objects, we decided to return an objectDTO instead of the object when the GetAllObjects method in the controller is called.
Using Postman to query the Backend Server results to the proper behaviour, the retrieved list has all the properties of the DTO.
Problem arises when using the Mobile Client. According to the logs, an "HTTP Error 400.0 - Bad Request" happened and "The request could not be understood by the server due to malformed syntax." is indicated under "More Information."
I dont know why this error happened. I updated the Object class in the Client App to match the ObjectDTO class in the server. For comparison:
ObjectDTO in Server
public class SaleDto
{
public string Id { get; set; }
public string ProductId { get; set; }
public string PromoterId { get; set; }
public string StoreId { get; set; }
public string PaymentMethodId { get; set; }
public bool CorporateSale { get; set; }
public DateTime? DateSold { get; set; }
public double PriceSold { get; set; }
public int QuantitySold { get; set; }
public string Remarks { get; set; }
public bool Deleted { get; set; }
public DateTimeOffset? CreatedAt { get; set; }
public DateTimeOffset? UpdatedAt { get; set; }
public byte[] Version { get; set; }
public string ProductSku { get; set; }
public string ProductPartNumber { get; set; }
public string StoreName { get; set; }
public string PaymentMethodName { get; set; }
}
Object Model in Client App
public class Sale
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "productId")]
public string ProductId { get; set; }
[JsonProperty(PropertyName = "promoterId")]
public string PromoterId { get; set; }
[JsonProperty(PropertyName = "storeId")]
public string StoreId { get; set; }
[JsonProperty(PropertyName = "paymentMethodId")]
public string PaymentMethodId { get; set; }
[JsonProperty(PropertyName = "corporateSale")]
public bool CorporateSale { get; set; }
[JsonProperty(PropertyName = "dateSold")]
public DateTime? DateSold { get; set; }
[JsonProperty(PropertyName = "priceSold")]
public double PriceSold { get; set; }
[JsonProperty(PropertyName = "quantitySold")]
public int QuantitySold { get; set; }
[JsonProperty(PropertyName = "remarks")]
public string Remarks { get; set; }
[JsonProperty(PropertyName = "deleted")]
public bool Deleted { get; set; }
[JsonProperty(PropertyName = "createdAt")]
public DateTime CreatedAt { get; set; }
[JsonProperty(PropertyName = "updatedAt")]
public DateTime UpdatedAt { get; set; }
[JsonProperty(PropertyName = "version")]
public string Version { get; set; }
[JsonProperty(PropertyName = "productSku")]
public string ProductSku { get; set; }
[JsonProperty(PropertyName = "productPartNumber")]
public string ProductPartNumber { get; set; }
[JsonProperty(PropertyName = "storeName")]
public string StoreName { get; set; }
[JsonProperty(PropertyName = "paymentMethodName")]
public string PaymentMethodName { get; set; }
public virtual Product Product { get; set;}
public virtual Store Store { get; set; }
public virtual PaymentMethod PaymentMethod { get; set; }
}
Or it might be because of the Sync Tables? Here's the code that handles syncing (stuff has been omitted for brevity)
public class DataStore
{
private static DataStore _instance;
public MobileServiceClient MobileService { get; set; }
IMobileServiceSyncTable<Sale> saleTable;
public static DataStore Instance
{
get
{
if (_instance == null)
{
_instance = new DataStore();
}
return _instance;
}
}
private DataStore()
{
MobileService = new MobileServiceClient("url");
var store = new MobileServiceSQLiteStore("tabletable.db");
store.DefineTable<Sale>();
MobileService.SyncContext.InitializeAsync(store);
saleTable = MobileService.GetSyncTable<Sale>();
}
public async Task<Sale> AddSaleAsync(Sale sale)
{
await saleTable.InsertAsync(sale);
bool wasPushed = await SyncSalesAsync();
if (wasPushed) return null;
return sale;
}
public async Task<List<Sale>> GetSalesAsync(int take = 20, int skip = 0)
{
IEnumerable<Sale> items = await saleTable
.Where(sale => !sale.Deleted)
.OrderByDescending(sale => sale.CreatedAt)
.Take(take)
.Skip(skip)
.ToEnumerableAsync();
return new List<Sale>(items);
}
public async Task<bool> SyncSalesAsync()
{
ReadOnlyCollection<MobileServiceTableOperationError> syncErrors = null;
bool wasPushed = true;
try
{
await MobileService.SyncContext.PushAsync();
await saleTable.PullAsync("allSales", saleTable.CreateQuery());
}
catch (Exception e)
{
Debug.WriteLine(#"/Sale/ Catch all. Sync error: {0}", e.Message);
Debug.WriteLine(e.StackTrace);
}
return wasPushed;
}
}
Any kind of help will be much appreciated.
Having SaleDto extend/implement EntityData solved the problem
public class SaleDto : EntityData

Complex Automapper Configuration

I'm mapping from an existing database to a DTO and back again use Automapper (4.1.1) and I've hit a few small problems.
I have a (simplified) model for the database table:
public class USER_DETAILS
{
[Key]
public string UDT_LOGIN { get; set; }
public string UDT_USER_NAME { get; set; }
public string UDT_INITIALS { get; set; }
public string UDT_USER_GROUP { get; set; }
public decimal UDT_CLAIM_LIMIT { get; set; }
public string UDT_CLAIM_CCY { get; set; }
}
and a DTO object:
public class User
{
public string Login { get; set; }
public string UserName { get; set; }
public string Initials { get; set; }
public string UserGroup { get; set; }
public double ClaimLimit { get; set; }
public string ClaimCurrency { get; set; }
}
I've created a profile
public class FromProfile : Profile
{
protected override void Configure()
{
this.RecognizePrefixes("UDT_");
this.ReplaceMemberName("CCY", "Currency");
this.SourceMemberNamingConvention = new UpperUnderscoreNamingConvention();
this.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
this.CreateMap<decimal, double>().ConvertUsing((decimal src) => (double)src);
this.CreateMap<USER_DETAILS, User>();
}
}
However, it seems that Automapper doesn't like combining this many settings in the config. Even simplifying the models, I can't get
this.RecognizePrefixes("UDT_");
this.ReplaceMemberName("CCY", "Currency");
to work together, and whilst
this.CreateMap<decimal, double>().ConvertUsing((decimal src) => (double)src);
works ok with the models in the test, it fails when using it against a database.
Is there a way to get all this to work together, or should I fall back to using ForMember(). I was really hoping I could get this working as there are a lot of tables in this system, and I'd rather not have to do each one individually.
You will need to extend this for other types, only tested with strings, I have an extension method that does all the work and looks for unmapped properties.
public class USER_DETAILS
{
public string UDT_LOGIN { get; set; }
public string UDT_USER_NAME { get; set; }
public string UDT_INITIALS { get; set; }
public string UDT_USER_GROUP { get; set; }
// public decimal UDT_CLAIM_LIMIT { get; set; }
public string UDT_CLAIM_CCY { get; set; }
}
public class User
{
public string Login { get; set; }
public string UserName { get; set; }
public string Initials { get; set; }
public string UserGroup { get; set; }
//public double ClaimLimit { get; set; }
public string ClaimCurrency { get; set; }
}
public static class AutoMapperExtensions
{
public static IMappingExpression<TSource, TDestination>
CustomPropertyMapper<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
var sourceType = typeof(TSource);
var destinationType = typeof(TDestination);
var existingMaps = Mapper.GetAllTypeMaps().First(x => x.SourceType == sourceType && x.DestinationType == destinationType);
var properties = sourceType.GetProperties();
foreach (var property in existingMaps.GetUnmappedPropertyNames())
{
var similarPropertyName =
properties.FirstOrDefault(x => x.Name.Replace("_", "").Replace("UDT", "").ToLower().Contains(property.ToLower()));
if(similarPropertyName == null)
continue;
var myPropInfo = sourceType.GetProperty(similarPropertyName.Name);
expression.ForMember(property, opt => opt.MapFrom<string>(myPropInfo.Name));
}
return expression;
}
}
class Program
{
static void Main(string[] args)
{
InitializeAutomapper();
var userDetails = new USER_DETAILS
{
UDT_LOGIN = "Labi-Login",
UDT_USER_NAME = "Labi-UserName",
UDT_INITIALS = "L"
};
var mapped = Mapper.Map<User>(userDetails);
}
static void InitializeAutomapper()
{
Mapper.CreateMap<USER_DETAILS, User>().CustomPropertyMapper();
}
}
}

How to perform a more complex query with AutoQuery

Given the following definitions from a ServiceStack endpoint:
public class LoanQueue
{
public int LoanId { get; set; }
public DateTime Submitted { get; set; }
public DateTime Funded { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Fico { get; set; }
public int Fraud { get; set; }
public int CDS { get; set; }
public int IDA { get; set; }
public string Income { get; set; }
public string Liabilities { get; set; }
public string Agent { get; set; }
public string Status { get; set; }
public string State { get; set; }
public string Product { get; set; }
public string Comment { get; set; }
}
public enum DateType
{
None,
Submitted,
Funded
}
[Route("/loan/queue/search", "GET")]
public class LoanQueueQueryGet : QueryBase<LoanQueue>
{
public DateType DateType { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public string AgentUserName { get; set; }
public Languange Languange { get; set; }
public bool WorkingLoan { get; set; }
public bool MicrobusinessLoan { get; set; }
public LoanStatus LoanStatus { get; set; }
}
public object Get(LoanQueueQueryGet request)
{
if (request == null) throw new ArgumentNullException("request");
var profiler = Profiler.Current;
using (profiler.Step("LoanServices.LoanQueue"))
{
SqlExpression<LoanQueue> q = AutoQuery.CreateQuery(request, Request.GetRequestParams());
QueryResponse<LoanQueue> loanQueueResponse = AutoQuery.Execute(request, q);
return loanQueueResponse;
}
}
My question is this, "Is it even possible to run conditional logic based on the request object in the service impl"? e.g.
If DateType == DateType.Submitted
then query the Submitted property on the LoanQueue with a BETWEEN clause (StartDate/EndDate) or
If DateType == DateType.Funded
then query the Funded property on the LoanQueue with a BETWEEN clause (StartDate/EndDate).
My guess is that I'm trying to bend AutoQuery too far and would be better served just coding it up the old fashion way. I really like the baked-in features of the AutoQuery plugin and I'm sure there will be times when it will suit my needs.
Thank you,
Stephen
AutoQuery will ignore any unmatched fields so you're able to use them to extend your populated AutoQuery with additional custom logic, e.g:
public object Get(LoanQueueQueryGet request)
{
var q = AutoQuery.CreateQuery(request, Request.GetRequestParams());
if (request.DateType == DateType.Submitted)
{
q.And(x => x.Submitted >= request.StartDate && x.Submitted < request.EndDate);
}
else
{
q.And(x => x.Funded >= request.StartDate && x.Funded < request.EndDate);
}
var loanQueueResponse = AutoQuery.Execute(request, q);
return loanQueueResponse;
}

EF Code First - Many To Many

I have a problem with devising a many to many relationship in code first. EF is creating the Junction table and associating the Fk's as I would expect, however when i try to access the User's MailingList collection, there are no entries.
I've implemented test data on Initialise via Seeding, the data is al present in the database.
I think the problem lies with the constructors for Users and MailingLists, but I'm uncertain. I want to be able to navigate the navigational property of User.MailingLists.
var user = db.Users.Find(1);
Console.WriteLine("{0}", user.EmailAddress); //This is Fine
Console.WriteLine("{0}", user.Address.PostCode); /This is Fine
foreach (MailingList ml in user.MailingLists) // this is 0
{
Console.WriteLine("{0}", ml.Name);
}
My model is below:-
public class User : IEntityBase
{
public User()
{
MailingLists = new List<MailingList>();
}
public int Id { get; set; }
public string Forename { get; set; }
public string Surname { get; set; }
public string EmailAddress { get; set; }
public DateTime? DateLastUpdated { get; set; }
public DateTime DateCreated { get; set; }
public bool IsDeleted { get; set; }
public virtual Address Address { get; set; }
public ICollection<MailingList> MailingLists { get; set; }
}
public class MailingList : IEntityBase
{
public MailingList()
{
Users = new List<User>();
}
public int Id { get; set; }
public string Name { get; set; }
public DateTime? DateLastUpdated { get; set; }
public DateTime DateCreated { get; set; }
public bool IsDeleted { get; set; }
public ICollection<User> Users { get; set; }
}
public class Address : IEntityBase
{
public int Id { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string AddressLine3 { get; set; }
public string City { get; set; }
public string County { get; set; }
public string PostCode { get; set; }
public DateTime? DateLastUpdated { get; set; }
public DateTime DateCreated { get; set; }
public bool IsDeleted { get; set; }
}
Any suggestions welcome.
You are neither eager loading the MailingList entries with the query, nor fulfulling the requirements for a lazy loading proxy so there is no way EF can populate the collection.
To allow lazy loading, change the MailingList property to be virtual to allow the EF proxy to override it.
To use eager loading, use Include() (an extension method in System.Data.Entity) in the query to specify that the MailingList should be loaded.

Resources