Has anyone solved the riddle of how to apply SpecFlow Step Argument Transformations to cells in a table, in conjunction with the SpecFlow.Assist CreateInstance/CreateSet? (code combined here to save space)
Given a table like the following:
| Price | Zip | Effective Date |
| 10.00 | 90210 | in 2 days |
When the 'given' step executes
And the table data populates a poco
Then the effective date should be transformed into a DateTime with value of 2 days from today
[Given(#"a table like the following:")]
public void GivenATableLikeTheFollowing(Table table)
{
var temp = table.CreateInstance<Temp>();
}
internal class Temp
{
decimal Price { get; set; }
int Zip { get; set; }
DateTime EffectiveDate { get; set; }
}
[Binding]
public class Transforms
{
[StepArgumentTransformation(#"in (\d+) days?")]
public DateTime InXDaysTransform(int days)
{
return DateTime.Today.AddDays(days);
}
}
StepArgumentTransformation bindings apparently don't apply to table cell contents (since the step's argument is type Table), but somehow the SpecFlow.Assist CreateInstance/CreateSet will still transform cell data for basic types.
For example , if the Effective Date's contents are '11/13/2016' instead of 'in 2 days', the underlying poco's EffectiveDate property transforms to a DateTime just fine (or an int, decimal, etc).
I see some other solutions like applying a conversion within the step definition itself like here or creating a StepArgumentTransformation for the whole table, but... obvious cons. Update: this question is similar, but solutions also avoid mingling StepArgumentTransformation with CreateInstance/CreateSet.
There is also a section in the SpecFlow Assist Helpers docs about extending by registering value retrievers/comparers, but in my example, a DateTime set already exists. So, perhaps a custom DateTime type? It seems like perhaps there could be a check for StepArgumentTransformations on the known types, or something like that.
In the DateTime retriever, something like..
public virtual DateTime GetValue(string value)
{
var returnValue = DateTime.MinValue;
// check for StepArgumentTransformations here first?
DateTime.TryParse(value, out returnValue);
return returnValue;
}
Any ideas on what I am missing to get the StepArgumentTransformation to apply to the table cell contents when using table.CreateInstance? Or is one of the mentioned solutions the best/only way?
I have created a small prototype that can be used to reconfigure Assist to be able to pick up conversions with [StepArgumentTransformation] bindings.
My plan is to make a blog post about it, but until it is ready, maybe you can get out the essence from this gist. (I did it a year ago for SpecFlow v2.0, so some smaller adaptions might be necessary.)
https://gist.github.com/gasparnagy/a478e5b7ccb8f557a6dc
I don't think what you want is implemented currently, but theoretically I think it could be implemented. You can probably implement a new, enhanced DateTimeValueRetriever yourself which checks to see if the string is parseable as a datetime first and if not checks if any of the [StepArgumentTransformation] methods can parse it, and then replace the current DateTimeValueRetriever with your enhanced one. Then you could submit a pr offering your new version as an enhancement to the existing version, and see what the appetite is.
Related
If I have a Techtalk.Specflow.Table, is it possible to detect automatically the appropriate types of the elements in the table ?
For exemple, if I have the following steps :
Given the following ticket sold on the 2019-01-01
|TicketId|Owner |Amount|Seat|
|0033 |John Doe |20.00 |3F |
If I define my step like this
[Given(#"Given the following ticket sold on the (.*)")]
public void GivenTheFollowingPosition(DateTime date, Table table)
{
}
Specflow is capable of detecting and cast the date correctly. Therefore I assume it should also be capable of doing so for the elements of the table. Do you know if it is possible to achieve it and how ?
Have a nice day
Have a look at SpecFlow Assist Helpers. There are a few helpful methods, you can try to use table.CreateInstance<T> method to convert row in your table to object for future use. You can also specify the custom mapping using TableAliases attribute, see Working Effectively with SpecFlow Tables article for details
I have a list that comes from a database and want to sort the DateofCreation variable from that list. It's datatype is in string.
How do I go about it?
I went about doing what was instructed in this site here
and revised it to fit my code, the error is
Error CS0834 A lambda expression with a statement body cannot be converted to an expression tree
var orderedList3 = collectionRecords.OrderByDescending(x =>
{
DateTime dt;
if (!DateTime.TryParse(x.DateOfCreation, out dt)) return DateTime.MaxValue;
return dt;
});
collectionRecords comes from
public class CollectionRecords
{
[Key]
...
...
public string AuthorSource { get; set; }
public string DateOfCreation { get; set; }
public string VolumeIssueNo { get; set; }
...
}
Sample Input:
undated
1991 May 8
march 2012
various dates
Apr 8 2018
Expected Output:
Apr 8 2018
March 2012
1991 May 8
various dates
undated
Firstly, you should not be using strings as dates, that is why you end up having to convert them to DateTime first before ordering. If you ever need to store a date store it as a date, not as a string.
Expected value is as well wrong as it seems. If I run your code through LinqPad I get a correct output of
Apr 8 2018
march 2012
1991 May 8
various dates
undated
but this requires DateTime.MinValue not Max value to be returned.
As well I get the same output in VS. What is the context of collectionRecords? It maybe IQuerable? I think if you try to convert it to an array or IEnumerable before ordering it should work like a charm.
UPDATED:
How do you store it in the Database? as a string or as a date? Create a migration to the database and store it as a date, unless you are specifically required to store those as strings and store such values as "various dates" or "Undated", which is a bit wrong as you are breaking the concept of DateTime. You should not store anything else in a DateTime Database fields, or store DateTime type fields in a string/vrchar type columns. It is better to have two separate columns in Database, for example: Datetime type column which stores only the dates and have a boolean type column like "IsUdated".
Of course it all depends on the software you are working if you can make those changes, otherwise, you need to do what you are already doing, just make sure, the types you are using for expression are correct, not IQuerable, but a list or an IEnumerable.
You could implement IComparable interface, and override method CompareTo, to decide the default sorting order.
You should probably listen to the other people commenting about the database structure, but to get around the expression tree error you can execute the statement on the DB first by:
var orderedList3 = collectionRecords.ToList().OrderByDescending(x =>
{
DateTime dt;
if (!DateTime.TryParse(x.DateOfCreation, out dt)) return DateTime.MaxValue;
return dt;
});
Going forward not a good idea to have this at all especially if you have more data.
I have a RDLC report that displays count of Test Packs issued each day. I want to change this report in such a way that it should show the count of test packs issued per week instead of per day. How can i achieve that?
Here is the result of the current report (exported to excel).
The report is generated Island wise, then issue date wise test packs count.
The property in my ViewModel returns a collection that contains Test Pack records with fields like
public class TestPack
{
public string TestPackNo { get;set; }
public string Island { get;set; }
public string IssueDate { get;set; }
}
There two ways to achieve this.
Work with underlying data - for example, add column "MondayofCurrentWeek" (since every week would have Monday) and group by that column instead. (This would be my preferred solution.)
Add group by "week start" expression. You might need to add function to report's code to find it base on current date function. Since the language for report's code is VB, this may help: https://msdn.microsoft.com/en-us/library/aa227527(v=vs.60).aspx
I have a field in an MVC 5 C# model that maps to an SQL table. The source data is of type nvarchar(10) for the needs of others who also use the table.
In the latest iteration, the users also want to be able to sort by this column, which means that I need to convert this to a datetime value (so it can be correctly sorted) and display as a shortdate.
I know I can make this field private and create a separate public function that casts this as a date, but I was wondering if there was a more compact way I could do this all in one function. I have searched around, but not seen any examples of what I am describing. Is this even possible?
Stated another way, I want to display this as a shortdate but sort it as a date. Am I on the right track, or am I missing something?
[Required]
[StringLength(10)]
[Display(Name = "Entry Date")]
[DisplayFormat(DataFormatString="{0:d}")]
public string EntryDate { get; set; }
If the data represents a date, add a property which is a date:
public DateTime? EntryDateValue
{
get
{
DateTime dateValue;
if (DateTime.TryParse(EntryDate, out dateValue))
return dateValue;
return null;
}
set
{
// parse "value" to the string format you want
// and store it in EntryDate
}
}
Bind the view to that property instead, formatting the output with .ToString("...") as needed, and allow the sorting to take place on the DateTime? rather than on the string. Essentially creating a pass-through property for the application code, obscuring the backing "stringly-typed" value.
In general, it's easier to tweak the correct backing data for text display than it is to tweak the text display to act like backing data.
I have started using Subsonic for developing a small CMS web application.
I noticed that when ever I call the .Save() method of the Subsonic-Table-Object, that my date field called 'CreatedOn' gets overwritten with today's date.
Subsonic Info: Version 3.0.0.4 using ActiveRecord
// add article from dataset
Article a = new Article();
a.Title = article["title"].ToString();
a.Synopsis = article["teaser"].ToString();
a.Body = article["body"].ToString();
a.Keywords = string.Empty;
a.Photo = string.Empty;
///articles/2010/04/14/deregistration-of-trade-unions
a.CreatedOn = DateTime.Parse(sDestin.Substring(9, 10));
a.UpdatedOn = DateTime.Parse(sDestin.Substring(9, 10));
a.Save();
Before saving, the CreatedOn field is set to the correct date (same as UpdatedOn).
As soon as the .Save() method is run, the CreatedOn field changes to today's date. The UpdatedOn date field maintains the same date that was assigned to it.
What I have tried:
I've double checked the Article table, there are no default values such as (GetDate()) present for any of the date fields.
I've rebuilt my templates several times.
I've also attempted to reset and save the CreatedOn date field a second time.
Any help would be appreciated.
Answer
After further investigation I found the problem.
Subsonic appears to be generating a line of code which is manipulating the date.
I dont know why its doing this only for the CreatedOn date on not the UpdatedOn date. Both fields are identical in structure (MSSQL 2005).
Extracted from the Save() method of my article object.
public void Add(IDataProvider provider){
//this.CreatedOn=CMSDB.DateTimeNowTruncatedDownToSecond(); // commenting this out works, CreatedOn is no longer overwritten
var key=KeyValue();
if(key==null){
var newKey=_repo.Add(this,provider);
this.SetKeyValue(newKey);
}else{
_repo.Add(this,provider);
}
SetIsNew(false);
OnSaved();
}
Here is the anomolous context method Subsonic created:
internal static DateTime DateTimeNowTruncatedDownToSecond() {
var now = DateTime.Now;
return now.AddTicks(-now.Ticks % TimeSpan.TicksPerSecond);
}
Note: Stackoverflow required me to wait 6 hours to post the answer so I placed it here instead.
"CreatedOn" is a convention in SS ActiveRecord - when a field is called "CreatedOn" the code that you see to set the date is added by the T4 templates. It clearly isn't helpful in your case.
Two options:
edit your T4 template to remove the offending line (so you will not be able to take advantage of the auto-time-stamping in any of your model objects)
change the name of the field to something else
If accepted answer is not applicable for you as with my case, you might do a
new InlineQuery().Execute(string query)
to update the said fields.