I'm trying out the specFlow assist and not sure how would one create class property from table.
Imagine I have this class:
public class Tracking
{
public string Category { get; set; }
}
public class ODARequest
{
public string Title { get; set; }
public string Name { get; set; }
public Tracking Tracking { get; set; }
}
My Given scenario is next:
Scenario: Successfully create an account
Given I have entered the following data into the ODA form:
| Field | Value |
| Title | Mr |
| Name | Andy |
| Tracking Category | MDA |
public void GivenIHaveEnteredTheFollowingDataIntoTheODAForm(Table table)
{
var request = table.CreateInstance<ODARequest>();
}
The Tracking property will not be populated. Anyone know how to describe Tracking.Category in the table for this situation?
I haven't been able to find a way of getting specflow to map "CreateInstance" to non-standard data types properties.
However, in this case you could at least use a StepArgumentTransformation as follows:
[Given(#"I have entered the following data into the ODA form:")]
public void GivenIHaveEnteredTheFollowingDataIntoTheODAForm(ODARequest request)
{
Assert.IsNotNull(request.Tracking);
}
[StepArgumentTransformation(#".*")]
public ODARequest StringToTracking(Table input)
{
return new ODARequest() {
Title = input.Rows.Single(row => row["Title"])["value"],
Name = input.Rows.Single(row => row["Name"])["value"],
Tracking = new Tracking()
{ Category = input.Rows.Single(row => row["Field"] == "Tracking Category")["Value"] }
};
}
With a little work you could tidy the stepargumenttransformation up to accept each parameter as optional (rather than having "single()" throw if it is omitted).
I feel like there really ought to be a better way to do this, more like your original suggested code.
Hope this is helpful.
I've been hitting this problem and been thinking how to do it with a single step.
Scenario: Successfully create an account
Given I have entered the following data into the ODA form:
|Title | Name | Category|
| Mr | Andy | MDA |
public void GivenIHaveEnteredTheFollowingDataIntoTheODAForm(Table table)
{
var request = table.CreateInstance<ODARequest>();
request.Tracking = table.CreateInstance<Tracking>();
}
How it works:
You may call "CreateInstance" for each complex property you have so specflow will create you an instance. So you can have a single table with properties from different types.
By doing so you won't need a different step and the need of sharing data between the steps.
The drawback is that you may end up with a huge table if your class has a lot of properties with different types.
Note: As #Alex M commented it there is a risk when the classes are having a property with same name. What will happen actually is that both instances will get the same value due to the property name match on both classes.
Another possibility without modifying the specflow assist itself is to separate the subclass properties into separate Given.
Can be as per below example:
Scenario: Successfully create an account
Given I have entered the following data into the ODA form:
| Field | Value |
| Title | Mr |
| Name | Andy |
And the tracking info as:
| Tracking Category | MDA |
then my step definitions would be as:
[Given(#"I have entered the following data into the ODA form:")]
public void GivenIHaveEnteredTheFollowingDataIntoTheODAForm(Table table)
{
var request = table.CreateInstance<ODARequest>();
ScenarioContext.Current.Set(request, "request");
}
[Given(#"the tracking info as:")]
public void GivenTheTrackingInfoAs(Table table)
{
var request = ScenarioContext.Current.Get<ODARequest>("request");
request.TrackingFields = table.CreateInstance<Tracking>();
}
Otherwise the possibility is to contribute to specflow assist development.
Related
I am using the Neo4j .Net client to interface with a Neo4j database.
I often come by this issue when trying to map related nodes to nested classes :
For example mapping
(:Foo)-[:FOOBAR]->(:Bar)
to the form
public class Foo {
public string FooPropOne { get; set; }
public string FooPropTwo { get; set; }
public List<Bar> Bars { get; set; }
}
public class Bar {
public string BarPropOne { get; set; }
public string BarPropTwo { get; set; }
}
To get the correct output for deserialzation I have to write the query like so:
Foo foo = WebApiConfig.GraphClient.Cypher
.Match("(f:Foo)-[:FOOBAR]->(b:Bar)")
.With("#{
FooPropOne: f.FooPropOne,
FooPropTwo: f.FooPropTwo,
Bars: collect(b)
} AS Result")
.Return<Foo>("Result")
.SingleOrDefault();
To a certain extent this is fine, and works perfectly, but becomes quite unwieldy the more properties the Foo class has.
I was wondering if there was something I can do in the "With" statement to add a temporary property (in the above case "Bars") when returning the nodes, that does not require me to write every single property on the parent node?
Something like
.With("f.Bars = collect(b)")
.Return<Foo>("f")
that wont actually affect the data stored in the DB?
Thanks in advance for any relpies!!
I don't know of a way to add a temporary property like you want, personally - I would take one of two approaches, and the decision really comes down to what you like to do.
Option 1
The choosing properties version
var foo = client.Cypher
.Match("(f:Foo)-[:FOOBAR]->(b:Bar)")
.With("f, collect(b) as bars")
.Return((f, bars) => new Foo
{
FooPropOne = f.As<Foo>().FooPropOne,
FooPropTwo = f.As<Foo>().FooPropTwo,
Bars = Return.As<List<Bar>>("bars")
}).Results.SingleOrDefault();
This version allows you to be Type Safe with your objects you're bringing out, which on a class with lots of properties does limit the chance of a typo creeping in. This will give you back a Foo instance, so no need to do any other processing.
Option 2
The little bit of post-processing required version
var notQuiteFoo = client.Cypher
.Match("(f:Foo)-[:FOOBAR]->(b:Bar)")
.With("f, collect(b) as bars")
.Return((f, bars) => new
{
Foo = f.As<Foo>(),
Bars = bars.As<List<Bar>>()
}).Results;
This saves on the typing out of the properties, so in a big class, a time saver, and also means if you add extra properties you don't need to remember to update the query. However you do need to do some post-query processing, something like:
IEnumerable<Foo> foos = notQuiteFoo.Results.Select(r => {r.Foo.Bars = r.Bars; return r.Foo;});
Personally - I like Option 2 as it has less potential for Typos, though I appreciate neither is exactly what you're after.
I am in doubt about how to implement the Web API Routing when you have multiple DTOs for the same POCO.
Let us imagine the following scenario:
you have a POCO Class with. Let's say 100 properties
you need to display lists of that class in different platforms/UIs
i.e.
List 1 Prop A1 | Prop A2 | Prop A3 | Prop A4
List 2 Prop A21 | Prop A22 | Prop A23 | Prop A24 | Prop A25 | Prop A26 | Prop A27 | Prop A28 |
I will call it Company, since there are many examples using this class name
Is it correct to implement a Web API + DTOs strategy like this?
Classes are:
Company (the POCO class)
CompanyDetailDTO
CompanyLightListDTO
CompanyMediumListDTO
Example:
public class CompaniesController : ApiController
{
private MultiTierWebApiContext db = new MultiTierWebApiContext();
private static readonly Expression<Func<Company, CompanyDetailDTO>> AsCompanyDetailDTO =
x => new CompanyDetailDTO
{
Name = x.Name,
Email = x.Email,
isCustomer = x.isCustomer,
isSupplier = x.isSupplier,
Id = x.Id
};
private static readonly Expression<Func<Company, CompanyMediumListDTO>> AsCompanyMediumListDTO =
x => new CompanyMediumListDTO
{
Name = x.Name,
Email = x.Email,
Id = x.Id
};
private static readonly Expression<Func<Company, CompanyLightListDTO>> AsCompanyLightListDTO =
x => new CompanyLightListDTO
{
Name = x.Name,
Id = x.Id
};
// GET: api/Companies/LightList
[Route("api/Companies/LightList")] **It works, but is this a correct way to do it?**
[ResponseType(typeof(CompanyLightListDTO))]
public IQueryable<CompanyLightListDTO>GetCompaniesLightList()
{
return db.Companies.Select(AsCompanyLightListDTO); // default
}
// GET: api/Companies/MediumList
[Route("api/Companies/MediumList")]
[ResponseType(typeof(CompanyMediumListDTO))]
public IQueryable<CompanyMediumListDTO> GetCompaniesMediumList()
{
return db.Companies.Select(AsCompanyMediumListDTO); // default
}
// remaining code removed for simplicity
}
Thanks in advance for further help.
I would say you are on the right track with only providing the relevant information related to the particular DTO. Don't provide more data than necessary.
If you look at the following walk-through you will see how they follow a similar pattern to what you have in your example.
Create a REST API with Attribute Routing in ASP.NET Web API 2
Quoting for reference:
Instead, I want this request to return a subset of the fields. Also, I
want it to return the author's name, rather than the author ID. To
accomplish this, we'll modify the controller methods to return a data
transfer object (DTO) instead of the EF model. A DTO is an object that
is designed only to carry data.
// Typed lambda expression for Select() method.
private static readonly Expression<Func<Book, BookDto>> AsBookDto =
x => new BookDto
{
Title = x.Title,
Author = x.Author.Name,
Genre = x.Genre
};
// GET api/Books
public IQueryable<BookDto> GetBooks()
{
return db.Books.Include(b => b.Author).Select(AsBookDto);
}
My advice would be to separate the transformation/projection functionality out of the controller (SoC) into their own service(s) (SRP) and inject them (DI) into the ApiController. It will keep your Controllers light.
I'm fairly new to ASP.NET MVC and need some help.
I have a model called "Project", simplified below:
public class Project
{
public int ProjId { get; set; }
public string StatusID { get; set; }
}
The StatusID is a 1 character string, and can be I (In Progress), C (Completed), or K (Killed). So when I get a list of projects from the database, it returns that 1 character status code.
However, when I pass that information to my view, I'd like to show the full description rather than the 1 character.
What's the best way to approach this situation?
Thanks!
The best way would be to create a one-to-many relationship with a Statuses table. Then create a separate view model with this information.
var vm = new ProjectVM
{
ProjId = project.ProjId,
StatusDescription = statuses.Find(project.StatusId).Description
};
Notices statuses is a repository API that finds the right status description.
I've done quite a bit of research and I'm not sure how I should proceed with this.
Usual localization would change only when the language changes, so Hello for french would be Bonjour but my application needs to have special keywords for for certain users so UserX might say "Hello" needs to be "Allo".
I would like to have resource key with IdentityName_resourceKey and if this key is present take it otherwize fall back to resourceKey.
I'm thinking I need a custom ResourceProvider but my implementation is a simple if statement so I would not want to write a complete resource provider.
I wrote a extension of DisplayName attribute which works fine but this is not very good as I will need one of those for every data annotation attributes and this would not work if I use resources directly in pages or controllers...
public class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
private readonly PropertyInfo _propertyInfo;
public LocalizedDisplayNameAttribute(string resourceKey, Type resourceType) : base(resourceKey)
{
var clientName = CustomMembership.Instance.CurrentUser.Client.Name;
_propertyInfo = resourceType.GetProperty(clientName + "_" + base.DisplayName, BindingFlags.Static | BindingFlags.Public)
?? resourceType.GetProperty(base.DisplayName, BindingFlags.Static | BindingFlags.Public);
}
public override string DisplayName
{
get
{
if (_propertyInfo == null)
{
return base.DisplayName;
}
return (string) _propertyInfo.GetValue(_propertyInfo.DeclaringType, null);
}
}
}
I'm looking for the best way to implement this with the least amount of code..
Thank you!
There is a better way, Data Annotations is your answer!
this is just a sample, you need go more deeper with System.Globalization.CultureInfo and Data Annotations (System.ComponentModel.DataAnnotations)
you can define your model class like this (assuming we have a resource file named CustomResourceValues with a value "strHello")
public class SomeObject(){
<Display(Name:="strHello", ResourceType:=GetType(My.Resources.CustomResourceValues))>
public string HelloMessage{ get; set; }
}
so, in our view the work must do it by the htmlhelper (assuming razor like render engine and the model is type of "SomeObject")
#Html.LabelFor(Function(x) x.HelloMessage)
basic info http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.displayattribute.resourcetype(v=vs.95).aspx
I'm still learning, but with the stackoverflow commnuties help, I've been able to get closer and closer.
What I have right now is a View "Index.aspx":
System.Web.Mvc.ViewPage<Data.Models.GetDealsModel>
The Model:
public class GetDealsModel
{
// set up the model
public string DealId { get; set; }
public string StreetAddress { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
public string Logo { get; set; }
public string Website { get; set; }
public string TotalRows { get; set; }
}
And the controller:
public ActionResult Index()
{
LinqToDealsDataContext db = new LinqToDealsDataContext();
XElement xmlTree = XElement.Parse("<Request><ZipCode>92612</ZipCode></Request>");
var deals = db.spSearchDeals(xmlTree);
return View(deals);
}
And with this configuration I'm now getting this error:
The model item passed into the dictionary is of type 'System.Data.Linq.SqlClient.SqlProvider+SingleResult`1[Data.Models.spSearchDealsResult]', but this dictionary requires a model item of type 'Data.Models.GetDealsModel'.
I'm guessing that there's an issue connecting my Controller to my Model... I'm not sure why. PLEASE help me connect this final peice.
NOTE: I do understand that eventually I should separate my logic in the controller into a Repository Pattern, but for now, this will do.
You need to translate the data coming back from this call:
var deals = db.spSearchDeals(xmlTree);
into a GetDealsModel type. So something like:
GetDealsModel dealsModel = new GetDealsModel()
{
DealId = deals.DealId,
StreetAddress = deals.StreetAddress,
....
};
return View(dealsModel);
The reason being that your View is strongly typed to take a GetDealsModel, but your deals variable is not of that type and it gives you that exception when you pass it to the View.
You should create object of type GetDealsModel, but your DB Query returns object of type Data.Models.spSearchDealsResult. Try something like:
return new GetDealsModel
{
DealId = deals.Id,
// other fields here
}
Add to your learning curve list the following items:
Repository Pattern
Ask yourself the following question: Why do I need a service layer?
Read Steven Sanderson's book. It teaches you to think in MVC.
The above applies to your problems because your issues are clearly related to having code in your Controllers that should be in your Model (ie, data access code should be in a repository class). Ie, you are not thinking in MVC.
Your model should include the necessary repository classes, eg, DealRepository.
You need a Service class to map the objects your repository digs out of your database to your model class: that way conversion problems are encapsulated into the Service Layer code.
If you do this, you can then write in your controller:
public ActionResult Index()
{
return(DealService.GetByZipcode(92612));
}
Where DealService.GetByZipcode basically just maps DealRepository.GetByZipcode(92612) to your model class and returns the mapping result.
The DealRepository.GetByZipcode method would be roughly:
public static DealEntity GetByZipcode(string zip)
{
LinqToDealsDataContext db = new LinqToDealsDataContext();
XElement xmlTree = XElement.Parse("<Request><ZipCode>" + zip + "</ZipCode></Request>");
var deals = db.spSearchDeals(xmlTree);
return deals;
}
The DealEntity class is just whatever Linq gives you for your table.
The reason WHY for all this:
The reason for this structure is as follows:
a. All you data access code is in one place: DealRepository. You can test and debug that independently of everything else.
b. The mapping code is all in one place: DealService. You can test and debug that independently of everything else.
c. In other words, you need to properly separate your concerns.
The problem with your existing code is precisely that you have NOT separated concerns. Ie, you have taken a dash of MVC and put it in a food processor and ended up with mush full of problems that are way more difficult to deal with than they need be.
Your model is mixed into your controller, there is no repository, no service layer.
So hold your horses just a while and take the time to read Steve Sanderson's book.
I would also try modelling a simpler problem. That xml parsing makes my head hurt even on a good day.
NOTE:
You could seriously improve your naming conventions. LinqToDealsDataContext? You're kidding, right?