I currently have a project where I am utilizing a custom library of business objects that we want to pass over-the-wire through WebAPI/WFC/etc as needed. One of the hurdles I am reaching is dealing with the deserialization of an object through WebAPI.
The objects themselves adhere to the Factory Pattern, and as such, don't have public parameterless constructors (they are marked protected) and use a Factory to create an instance of the object. There are several important reasons for doing this, but we would like to be able to use the objects without the need for intermediary classes/models to be created as well.
Because of this, when the model is being bound to, the WebAPI framework fails to create an instance of the object, citing the "no parameterless constructors" error. I need to find a way to be able to call the factory method and return the new object to either a formatter or binder (or something else which is a part of the deserialization process) somehow.
Is there a clear cut way (and documentation) on how to extend Web API to handle this situation without having to implement another framework on top of it? I would appreciate any help that you can provide.
Edit:
So I eneded up creating a new Model Binder and created the object through the factory class in BindModel(). By assigning the object to bindingContext.Model and then just manually deserializing the object, I was able to achieve what was needed, but I am not sure if its 100% the right way.
See below for my code:
Public Class FactoryModelBinder
Implements IModelBinder
Public Function MindModel(actionContext as HttpActionContext, bindingContext as ModelBindingContext) As Boolean Implements IModelBinder.BindModel
Dim type = bindingModel.ModelType
Dim attributes = type.GetCustomAttributes(FactoryAttribute, False)
If attributes.Length > 0 Then
Dim factoryAttribute As FactoryAttribute = DirectCast(attributes(0), FactoryAttribute)
bindingContext.Model = factoryAttribute.FactoryType.InvokeMember("Create", BindingFlags.InokveMethod Or BindingFlags.Public Or BindingFlags.Static, Nothing, Nothing, Nothing)
Dim data as FormDataCollection = New FormDataCollection(actionContext.Request.Content.ReadAsStringAsync().Result)
Dim dict as NameValueCollection = data.ReadAsNameValueCollection()
For Each item as String in dict.Keys
Dim pi as PropertyInfo = bindingContext.Model.GetType().GetProperty(item)
pi.SetValue(bindingContext.Model, dict(item), Nothing)
Next
Return True
End If
Return False
End Function
This code relies upon a custom attribute (FactoryAttribute) which specifies in the object class, the Type of the Factory so it can be used to call the Create() method.
I appreciate any input on this.
Related
I have been using the WebAPI in ASP.Net to allow access to our Entity Framework Objects.
The problem I am having is converting the objects to a custom POCO for our end customers to use.
I need to convert the Entity Framework Object to a custom POCO.
For example in our SQL Database we have Tbl_Person with the following properties
PersonID
FirstName
SureName
DateOFBirth
AnnualSalary
This table maps to an Entity Framework class Person with the same properties.
But I want to change the properties so that when a end customer accesses it they get a POCO like:
PersonID
Name
Age
SalaryRange
I also want to keep the current features such as JSON & XMLoutput and allow for OData queries.
I have been trying to 'Collect' the Odata Query and applyto my database context but this does not seem to be work correctly
Please see code example below:
Imports System.Net
Imports System.Web.Http
Imports System.Data.Entity
Public Class PeopleData
Inherits DbContext
Public Property People() As DbSet(Of Person)
End Class
Public Class Person
Public Property PersonID() As Integer
Public Property FirstName() As String
Public Property SureName() As String
Public Property DateOFBirth() As Date
Public Property AnnualSalary() As Integer
End Class
Public Class PeopleController
Inherits System.Web.Http.ApiController
Private db As New PeopleData
Function GetPeople(query As OData.Query.ODataQueryOptions(Of Person)) As IQueryable(Of apiPerson)
Dim pep = query.ApplyTo(db.People)
Dim resPep As New List(Of apiPerson)
For Each p In pep
resPep.Add(New apiPerson(p))
Next
Return resPep.AsQueryable
End Function
End Class
Public Class apiPerson
Public Sub New(ByVal p As Person)
PersonID = p.PersonID
Name = p.FirstName & " " & p.SureName
Age = Date.Now.Year - p.DateOFBirth.Year
If p.AnnualSalary > 15000 Then
SalaryRange = "High"
Else
SalaryRange = "Low"
End If
End Sub
Public Property PersonID() As Integer
Public Property Name() As String
Public Property Age() As Integer
Public Property SalaryRange() As String
End Class
I have two problems:
1) The API help pages don't populate and only produce this error: 'Sample not available.'
I like the dynamic help pages & that they pick up code comments, this is a really quick and easy way to maintain documentation. How can I get them to work with the ApiPerson?
2) If I try /api/people?$filter=Age eq 29 I get an error Type 'MvcApiPeople.Person' does not have a property 'Age'.
I understand that the LINQ Query is been passed to the 'Person' and that property does not exist but how can I 'Translate' queries to map to different properties in the actual Database Object?
Your action declaration should be
Function GetPeople(query As OData.Query.ODataQueryOptions(Of apiPerson)) As IQueryable(Of apiPerson)
i.e query parameter should be of type ODataQueryOptions (Of apiPerson) not ODataQueryOptions (Of Person).
Regarding your question 1:
I think your problem is that the apiPerson-class is missing a parameterless constructor.
Also, I found a blog post with some information on how to customize sample generation when the default generation mechanism doesn't work:
http://blogs.msdn.com/b/yaohuang1/archive/2012/10/13/asp-net-web-api-help-page-part-2-providing-custom-samples-on-the-help-page.aspx.
After playing around with many different solutions I decided the simplest way to control the information that is made available on an API and still allow iQueryable is to control the data at database level or at class level with Data Contract annotations.
I actually created views in my database to render the data exactly how I want it to appear for end customers. I felt this had the best benefits for performance and speed of implementation.
Thank you to all the guys who offered suggestions.
My team is using Entity Framework 4.3.0 - Code Only with POCO classes as our ORM. Right now we use DBSets of Classes to access our 'tables'
Public Property Customers As DbSet(Of Customers)
But often we are doing soft deletes based on a IsDeleted column in LINQ, and filtering our select statements accordingly:
Dim LiveCustomers =
From C In EM.Customers
Where C.DeleteFlag = False
What I would really like to do is, instead of writing every query to include this filter, create some lower level property (possibly at our inherited DbContext level) that provides the filtered set, while maintaining strong type.
I tried this:
Public Property Customers As DbSet(Of Customer)
Public Property Customers_Live As DbSet(Of Customer)
Get
Return From C In Customers
Where C.DeleteFlag = False
End Get
Set(value As DbSet(Of Customer))
Customers = value
End Set
End Property
However that yielded me this error:
Multiple object sets per type are not supported. The object sets 'Customers' and 'Customers_Live' can both contain instances of type '__.Customer'.
A quick check on google yielded this promising result (How to: Query Objects with Multiple Entity Sets per Type) But after updating my Connection String, I'm still getting the same error.
<add name="EntityManager"
providerName="System.Data.SqlClient"
connectionString="
Data Source=xxxxxx;
Initial Catalog=xxxxxx;
User Id=xxxxxx;
Password=xxxxxx;
MultipleActiveResultSets=True"/>
So my question is, how could I effectively create a LINQ view that allows me to apply filtering, without impacting the upstream usage too drastically?
Change your property like this:
Public Property Customers As DbSet(Of Customer)
Public Property Customers_Live As IQueryable(Of Customer)
Get
Return From C In Customers
Where C.DeleteFlag = False
End Get
End Property
This is slightly different, as you won't have things like Add() or Remove(), but for a view you typically wouldn't expect to have that kind of functionality. If you want to add a new one, or remove one you should use the normal Customers property.
You could have your POCO classes inherit from a new class that has a new method that would do the filtering for you. Add something like this to the new class
--PSEUDO CODE!--
Public Function Filtered() as IEnumerable(Of Out T)
Return (From x In Me Where x.DeleteFlag).ToList()
End Function
and you could call it like:
Dim LiveCustomers =
From C In EM.Customers.Filtered
Or you could create an Interface and do a dependancy injection when you call your linq query. You'll have to google that one :)
I'm building an MVC3 application using the Entity Framework. In the application my controllers talk to a service layer. One of the controllers is a TournamentController which uses the TournamentService. This service has some standard methods such as CreateTournament, UpdateTournament etc.
When I want to insert a new tournament I want the view to have a dropdownlist of possible sports for which the tournament can be organised. So in the TournamentController's Create method I fill the ViewBag.Sports with a list of possible sports. Now to obtain this list of sports I use _tournamentService.GetAllSports(). In this GetAllSports() method of the TournamentService I want to create an instance of the SportService so I can 'forward' the question to the right service.
All services use dependency injection in the constructor to inject their own repository, like so:
private ITournamentRepository _repo;
public TournamentService(ITournamentRepository repo) {
_repo = repo;
}
My GetAllSports() method in the TournamentService looks like this:
public IEnumerable<Sport> GetAllSports() {
ISportService sportService = new SportService();
return sportService.GetSports();
}
The problem is that by calling the new SportService() it expects me to hand it an ISportRepository like in the TournamentService, where ninject creates the TournamentRepository. Now I could do the following:
public IEnumerable<Sport> GetAllSports() {
ISportService sportService = new SportService(new SportRepository());
return sportService.GetSports();
}
But the problem with that is that each repository expects an IContext, which is normally handled by ninject as well. Furthermore, I don't want two separate contexts to be instantiated.
A possible solution I found myself is to do this:
private ITournamentRepository _repo;
private ISportService _sportService;
public TournamentService(ITournamentRepository repo, ISportService sportService) {
_repo = repo;
_sportService = sportService
}
But there's only one method in my TournamentService class that would actually use the _sportService so I figure this is a bit overkill to make it a class attribute.
Keep it simple, inject ISportService to your controller and call sportService.GetSports() directly from the controller!
Your last solution valid. Inject the necessary service as part of the constructor.
And if you're worried about multiple contexts, don't let two separate contexts be created then.
In your Ninject binding:
Bind<ITournamentRepository>().To<TournamentRepository>().InRequestScope();
See https://github.com/ninject/Ninject.Web.Common/wiki/InRequestScope
The last piece is the important part. It will only create one instance of TournamentRepository for the current request. Any other requesters of TournamentRepository will get this instance.
If you're already doing this, you're set, otherwise, just add InRequestScope and you're done. (keeping in mind you'll need a reference to Ninject.Web.Common)
Hope that helps.
EDIT:
Remo is correct, I wouldn't call this from a service either. Just call for your lookup data from the controller and populate your view model. The InRequestScope advice still holds.
I'm fairly new at event driven programming and using MVC so forgive me if this question seems like a non-question.
I'm trying to assign values to my singleton model class (Client). I have 2 options:
I can use the model class constructor to assign to itself like so:
Class Client{
public var name;
public var email;
public function Client(arg_name, arg_email){
this.name = arg_name;
this.email = arg_email;
}
}
I can use the controller to assign my values for me like so:
Class Controller{
public var client:Client = new Client();
public function assign(){
client.name = "booo";
client.email = "blaaah#email.com";
}
}
Which one of these is a better solution? :) The reason why I'm so confused is cause I've seen examples that just pass values to the model class and do #1 (and setting new variables such as [var fullname = fname + lname], yet I know for a fact that it is the controller's job to assign values to the model.
Better is relative.
The second example is what is commonly called "property injection".
Basically the first option is quicker, smaller, and much less code.
The second option is more readable and flexible.
Regardless of the Controller, injection has the benefit that you can create getters and setters for each property.
So basically you can have something happen such as dispatching an event whenever you change the value of the "name" property.
The flex source code is filled with this concept. For example whenever you change the width of a group, it doesn't just change the value, it performs checks and then sets the group as dirty so that other things can react to it.
Basically it makes it simple to have everything do it's own thing, and it's more Object oriented.
All that doesn't mean that it's actually better, but it is more common now to write code in the second option style.
Hope that helps.
This test is to check that I can return a ViewModel object by creating a customer and calling the Details() controller method.
[TestMethod()]
public void Can_View_AccountDetails()
{
AccountController target = new AccountController(null, null, null, null);
target.customer = new Customer { Id = 4, Active = true, BillingAddress_Id=1, ShippingAddress_Id=2 };
// Act
ActionResult result = target.Details();
// Assert
var resultData = ((ViewResult)result).ViewData.Model as AccountViewModel;
Assert.IsInstanceOfType(resultData, (typeof(AccountViewModel)));
}
'customer' is a member of the controller base class, which is then assigned in Initialize(). Initially I couldn't assign anything to it, but by setting it to 'public' rather than 'protected' I was able to use it in my test and avoided trying to call the base class Initialize() method.
EDIT: 'customer' is populate from a Repository object injected into the base class constructor.
Is this the right way to do this? It seems somehow wrong to change the accessibility level in order to get the test to work.
Also, although I'm trying to use Moq to create my tests, I'm not actually doing any mocking at all here which again, doesn't seem right.
I think your real problem is that the customer information "magically" shows up within the AccountController. The Customer instance should be injected into the AccountController from the outside since it is an external dependency. That being the case you would not have to make the customer property public because you pass it into the AccountController yourself.
Protected means it can only access from derived classes, so the test class would need to inherit the protected class.
Also you stubbed this as being Moq, but i don't see any Mock testing. You should be using interfaces that represent the Customer class so you can Mock the ICustomer interface.
You need to stub your Repository object and set it up so that it returns customer. Then, you don't need to expose .customer property as public (or internal) - you simply tell repository stub to return the one you'd like:
var repositoryStub = new Mock<IRepository>();
var customer = new Customer { /* ... */ };
repositoryStub.Setup(r => r.GetCustomer()).Returns(customer);
And naturally, you need to initialize your AccountContoller with stubbed dependency to repository (and other ones aswell, if needed):
var accountController = new AccountController(repositoryStub, ...);
This of course assumes your AccountController can take repository dependency.
So now, when you call Initialize() on base class, it should use stubbed repository and set your private .customer field to the one you specified it to return during stub setup.