Mapster from ExpandoObject to Dto - how to prevent setting missing keys to Null in Dto? - mapster

I have an object representing an update:
var update = new ExpandoObject();
update.AddSafely("Name", "Mary");
This property is part of a dto, like:
public class Dto
{
public string Name {get; set;}
public string Other {get; set;
}
var data = new Dto { Name = "John", Other = "SomeData" };
Now when I map, all (other) properties not in the source dictionary are set to their default.
// Name = "Mary" but Other = null
update.Adapt(data);

You need to use IgnoreNullValues

Related

Dealing with AutoFixture ConstructorInitializedMemberAssertion and a read-only property initialized inside the constructor

I want to verify a class using ConstructorInitializedMemberAssertion and a read-only property CreatedAt that is initialized to a default value inside the constructor:
public class MyClass
{
public MyClass(string name)
{
Name = name;
CreatedAt = DateTime.UtcNow;
}
public string Name { get; private set; }
public DateTime CreatedAt { get; private set; }
}
var fixture = new Fixture();
var ctorAssertion = fixture.Create<ConstructorInitializedMemberAssertion>();
ctorAssertion.Verify(typeof(MyClass));
This test throws the following exception:
AutoFixture.Idioms.ConstructorInitializedMemberException: No constructors with an argument that matches the read-only property 'CreatedAt' were found
I looked in the docs, but did not find any sample like that. I'm rather new with AutoFixture, so I may miss something.
How to exclude it from assertion or is there other way to deal with it?

"SearchAsync" InvalidCastException Null object cannot be converted to a value type

I use Azure search and a field in the search index can be null.
Which means my code
var result = await searchIndexClient.Documents.SearchAsync<SearchEntity>(query, parameters);
throws an exception
"InvalidCastException Null object cannot be converted to a value type."
for Csharp:
public class SearchEntity{
...
boolean NewField{ get; set; }
...
}
and index document:
{
"#odata.context": "...",
"value": [
{
"#search.score": 1,
...
"NewField": null,
...
I would like to tell SearchAsync(?) to set a default value to the property in SearchEntity if the received field is null.
Is it possible?
(I know I can receive a null and default it later but what is the fun in that?)
One possible solution would be to define the default values in your entity and tell the JSON serializer to ignore the null values from source at the time of serialization.
For example, consider the following entity definition:
public class SearchEntity
{
[JsonProperty(PropertyName = "newField")]
public bool NewField { get; set; } = true;
[JsonProperty(PropertyName = "dateTime")]
public DateTime DateTime { get; set; } = DateTime.Now.Date;
}
and here's the code for serialization. I am using NewtonSoft.Json for that and instructing it to ignore the null values:
string json = "{\"newField\": false, \"dateTime\": null}";
JsonSerializerSettings settings = new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore
};
var searchEntity = JsonConvert.DeserializeObject<SearchEntity>(json, settings);
Console.WriteLine($"SearchEntity.NewField: {searchEntity.NewField}");//Prints "true"
Console.WriteLine($"SearchEntity.DateTime: {searchEntity.DateTime.ToString("u")}");//Prints current date e.g. "2021-07-01 00:00:00Z"

MVC model binding complex type that has a property of complex type collection with Angular JS

I am developing an Web application using Angular JS and ASP.NET MVC. But I am having a problem with model binding complex type. I am uploading list of files, list of string basically. In the data base, they(list of string and list of file) are the same columns and table because I save the path after file is uploaded. For string I save the same column with file path. For example, a database table has Id(int) and Data(string) columns.
Then I save the string data posted by AngularJS in the Data column, for file path, in the data column as well. But the point is, I need to remember the order. For example, user add a text field and then enter value, then add file field dynamically and then choose file, then add a text field and enter value again. So the order must be [ "text 1 value" , File , "text 2 value" ]. But the problem is we cannot bind the list of data mixed both HttpPostedFileBase for file and string for text value. So what I did was created view models like below.
public class CreateBlogVM
{
[Required]
[MaxLength(250)]
public string Title { get; set; }
[Required]
public ContentModel[] TextContents { get; set; }
[Required]
public ContentFileModel[] Files { get; set; }
}
public class ContentModel
{
public int OrderNo { get; set; }
public string Content { get; set; }
}
public class ContentFileModel
{
public int OrderNo { get; set; }
public HttpPostedFileBase File { get; set; }
}
As you can see in the above, CreateBlogVM will be the ViewModel that I am binding now. That class will have two properties of complex type- TextContents and Files that I explained above what I was doing. So to remember the order, I created a complex type with OrderNo field (Client will pass this value) as you can see above since we cannot bind list of data something like this
[HttpPostedFileBase, String, String, HttpPostedFileBase]
But the problem is when I post data from Angular js, all values are null and not binding the data. Only the "Title" value is binding.
var textContents = new Array();
var photoContents = new Array();
for(var i=0; i<$scope.rows.length; i++)
{
if($scope.rows[i].type=="text")
{
var value = $scope.rows[i].value;
if (value == "" || value == null) {
showAlert("Text field should not be empty", "danger");
return;
}
else {
var content = { OrderNo: i, Content: value }
textContents.push(content)
}
}
else if($scope.rows[i].type=="photo")
{
var file = $scope.rows[i].file;
if(file==null)
{
showAlert("Photo file is required", "danger");
return;
}
else {
var content = { OrderNo: i, File: file };
photoContents.push(file);
}
}
}
var fd = new FormData();
fd.append('Title', $scope.title);
fd.append("TextContents", textContents);
fd.append("Files", photoContents);
$http.post("/Admin/Blog/Create", fd, {
transformRequest: angular.identity,
headers: { 'Content-Type': undefined }
})
.success(function () {
})
.error(function () {
});
Above code is how I submit data to server. When I post data, all values are null and mvc is not binding data. But when I bind values without using complex type like this
public JsonResult Create(HttpPostedFileBase files, String contents, String title)
But if I bind like above, I cannot order the files and string contents. So what is wrong with my code? How can I bind complex data that has list of complex type object properties?
Change the models so that you get a direct relationship between the Content and the associated File (the Order property is unnecessary)
public class CreateBlogVM
{
[Required]
[MaxLength(250)]
public string Title { get; set; }
public List<FileVM> Files { get; set; }
}
public class FileVM
{
[Required]
public string Content { get; set; }
[Required]
public HttpPostedFileBase Image { get; set; }
}
You can only append simple name/value pairs to FormData (not arrays of objects). In your loop, append the data using
var fd = new FormData();
fd.append('Title', $scope.title);
for (var i=0; i<$scope.rows.length; i++)
{
....
var content = $scope.rows[i].value;
fd.append('Files[' + i + '].Content', content);
....
var file = $scope.rows[i].file;
fd.append('Files[' + i + '].Image', file);
}
....
so that your generating the names with indexers that relate to your collection property (Files[0].Content, Files[0].Image, Files[1].Content etc)
Then your POST method signature will be
public JsonResult Create(CreateBlogVM model)

Dapper .Net : table column and model property type mismatch

Actually I have a query which returns result containing column(for ex.Address) of type varchar but the domain model for that table containing property of type object(for ex. Address Address).Because of which it trows error which says could not cast string to Comment.I cant figure out how to resolve this issue with dapper .net.
Code snippet:
IEnumerable<Account> resultList = conn.Query<Account>(#"
SELECT *
FROM Account
WHERE shopId = #ShopId",
new { ShopId = shopId });
The Account object is for example.
public class Account {
public int? Id {get;set;}
public string Name {get;set;}
public Address Address {get;set;}
public string Country {get;set;}
public int ShopId {get; set;}
}
As there is type mismatch between database table column(Address) and domain model property(Address) dapper throws exception.So is there is any way to map that properties though dapper..
Another option is to use Dapper's Multi-Mapping feature.
public class TheAccount
{
public int? Id { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
public string Country { get; set; }
public int ShopId { get; set; }
}
public class Address
{
public string Street { get; set; }
public string City { get; set; }
}
public class Class1
{
[Test]
public void MultiMappingTest()
{
var conn =
new SqlConnection(
#"Data Source=.\SQLEXPRESS; Integrated Security=true; Initial Catalog=MyDb");
conn.Open();
const string sql = "select Id = 1, Name = 'John Doe', Country = 'USA', ShopId = 99, " +
" Street = '123 Elm Street', City = 'Gotham'";
var result = conn.Query<TheAccount, Address, TheAccount>(sql,
(account, address) =>
{
account.Address = address;
return account;
}, splitOn: "Street").First();
Assert.That(result.Address.Street, Is.Not.Null);
Assert.That(result.Country, Is.Not.Null);
Assert.That(result.Name, Is.Not.Null);
}
}
The only issue I see with this is that you'll have to list all of the Account fields, followed by the Address fields in your select statement, to allow splitOn to work.
Since there is a type mismatch between your POCO and your database, you'll need to provide a mapping between the two.
public class Account {
public int? Id {get;set;}
public string Name {get;set;}
public string DBAddress {get;set;}
public Address Address
{
// Propbably optimize here to only create it once.
get { return new Address(this.DBAddress); }
}
public string Country {get;set;}
public int ShopId {get; set;}
}
Something like that - you match the db column to the property DBAddress (You need to provide an alias like SELECT Address as DBAddress instead of *) and provide a get method on your Address object which creates / reuses a type of Address with the contents of the db value.

DataGrid 'EditItem' is not allowed for this view when dragging multiple items

I have a datagrid which gets data like this:
public struct MyData
{
public string name { set; get; }
public string artist { set; get; }
public string location { set; get; }
}
DataGridTextColumn col1 = new DataGridTextColumn();
col4.Binding = new Binding("name");
dataGrid1.Columns.Add(col1);
dataGrid1.Items.Add((new MyData() { name = "Song1", artist = "MyName", location = "loc"}));
dataGrid1.Items.Add((new MyData() { name = "Song2", artist = "MyName", location = "loc2"}));
The problem is- whenever a user tries to edit a cell or drags multiple cells- the app throws an exception:
System.InvalidOperationException was unhandled
Message: 'EditItem' is not allowed for this view.
Why is this? Is it because of the way the data is entered?
Any ideas?
Thanks!
I got this issue when assigning ItemsSource to IEnumerable<T>.
I fixed it by converting the IEnumberable<T> to a List<T> and then assigning that to ItemsSource.
I'm not sure why using IEnumerable caused that issue, but this change fixed it for me.
Instead of using a struct use a class instead.
UPDATED ANSWER: Try adding your MyData instances to a List then assigning that list to the DataGrid.ItemsSource
If you use datagrid DataGridCheckBoxColumn you need to set <Setter Property="IsEditing" Value="true" />
on check box column. See this: https://stackoverflow.com/a/12244451/1643201
This answer is not my own, just the working code example suggested by AnthonyWJones.
public class MyData //Use class instead of struct
{
public string name { set; get; }
public string artist { set; get; }
public string location { set; get; }
}
DataGridTextColumn col1 = new DataGridTextColumn();
col4.Binding = new Binding("name");
dataGrid1.Columns.Add(col1);
dataGrid1.Items.Add((new MyData() { name = "Song1", artist = "MyName", location = "loc"}));
dataGrid1.Items.Add((new MyData() { name = "Song2", artist = "MyName", location = "loc2"}));
//Create a list of MyData instances
List<MyData> myDataItems = new List<MyData>();
myDataItems.Add(new MyData() { name = "Song1", artist = "MyName", location = "loc"});
myDataItems.Add(new MyData() { name = "Song2", artist = "MyName", location = "loc2"});
//Assign the list to the datagrid's ItemsSource
dataGrid1.ItemsSource = items;
For my case,
processLimits.OrderBy(c => c.Parameter);
returns an
IOrderedEnumerable<ProcessLimits>
not a
List<ProcessLimits>
so when I assign a style for my event setter to a checkbox column in my datagrid
style.Setters.Add(new EventSetter(System.Windows.Controls.Primitives.ToggleButton.CheckedEvent, new RoutedEventHandler(ServiceActiveChecked)));
ServiceActiveChecked is never called and I got
'EditItem' is not allowed for this view.
and for anyone else doing checkboxes in datagrid columns, I use a column object with my column data in this constructor for adding the data grid I use with adding the style above.
datagridName.Columns.Add(new DataGridCheckBoxColumn()
{
Header = column.HeaderText.Trim(),
Binding = new System.Windows.Data.Binding(column.BindingDataName.Trim()) { StringFormat = column.StringFormat != null ? column.StringFormat.Trim().ToString() : "" },
IsReadOnly = column.IsReadOnlyColumn,
Width = new DataGridLength(column.DataGridWidth, DataGridLengthUnitType.Star),
CellStyle = style,
});
I solved this by setting the datagrid's source after the InitializeComponent:
public MainWindow()
{
InitializeComponent();
FilterGrid.ItemsSource = ScrapeFilter;
}

Resources