Why is my OData V4 Identifier not working on my ASP.NET dataservice? - asp.net

I have a Problem using my OData V4 Service in ASP.NET when using integer keys. I am not using Entity Framework, as I get my data from a SOAP service.
Here is my data class:
public class RecipeDto
{
public RecipeDto();
public RecipeDto(string name);
public RecipeDto(int ident);
public string Description { get; set; }
public int Ident { get; set; }
public string Name { get; set; }
public List<RecipeVersionDto> Versions { get; set; }
}
And I set my key using fluent API:
var rtdto = builder.EntitySet<RecipeTemplateDto>("AgentConfigTemplates").EntityType
.HasKey(r => r.Ident)
.HasMany(r => r.Versions);
Here is my metadata on my service:
<EntityType Name="RecipeTemplateDto">
<Key>
<PropertyRef Name="Ident"/>
</Key>
<Property Name="Ident" Type="Edm.Int32" Nullable="false"/>
<Property Name="Description" Type="Edm.String"/>
<Property Name="Name" Type="Edm.String"/>
<NavigationProperty Name="Versions" Type="Collection(Ifmdatalink.Linerecorder.Backend.PlugIn.dto.RecipeTemplateVersionDto)"/>
</EntityType>
Now I would expect to get the first entry of my entity set by using this query:
GET http://localhost:13917/my.svc/AgentConfigTemplates(1)
But I always get the complete list.
Why is this happening and how can I get the first entry?
Do I have to extend my odata controller somehow?
If I put my key in quotes I get a bad request response.

The answer is neither in the model nor in the fluent api definition. It is a problem of the controller. An OData Controller needs to implement two get methods:
public async Task<IQueryable<AgentVersionDto>> Get()
public SingleResult<AgentDto> Get([FromODataUri] int key)
This covers a get for the whole entity set and for a single entry. If the latter is not implemented the odata service in webapi 2 will always return the whole list.

Related

.net core API optional route parameters

I have a .net core api with swagger. No I want to add a Filter-Class including optional filter-parameters.
[HttpGet("", Name ="get-index")]
[ProducesResponseType(typeof(IEnumerable<MyModelGet>), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(void), (int)HttpStatusCode.NoContent)]
public IActionResult GetIndex([FromRoute] MyFilter? filter){
...
}
The properties of the filter-class are optional/nullabel:
public class MyFilter {
public int? size{
get; set;
} = null;
...
}
But in Swagger all Properties are required:
Is there any way (e.g. a Annotation) to make this fields optional?
Replacing the [FromRoute] by [FromQuery] solved my issue.
just Add '?' in HttpGet , like This :
[HttpGet("", Name ="get-index?")]

.NET Core OData Action Parameter Null

My Odata Action Parameters are not resolving / deserializing.
I am using dotnet core 2.2 to surface an OData controller.
I need to implement an unbounded action. The action parameter (UserDto userDto) is not being deserialized by the OData routing engine:
[AllowAnonymous]
[HttpPost]
[ODataRoute(Routing.Endpoints.UserRoutes.AUTHENTICATE)]
public async Task<IActionResult> Authenticate(UserDto userDto)
{
var user = await _userService.Authenticate(userDto?.Username, userDto?.Password);
if (user == null)
return BadRequest("Username or password is incorrect");
var dto = Mapper.Map<UserDto>(user);
return Ok(dto);
}
Here is my configuration:
app.UseMvc(routeBuilder =>
{
var odataBuilder = new ODataConventionModelBuilder(app.ApplicationServices);
odataBuilder.EnableLowerCamelCase();
odataBuilder.EntitySet<BookDto>(nameof(Book));
odataBuilder.EntitySet<UserDto>(nameof(User));
var authenticate = odataBuilder.Action(Routing.Endpoints.UserRoutes.AUTHENTICATE);
authenticate.Parameter<UserDto>("userDto");
routeBuilder.Select().Expand().Filter().OrderBy().Count().MaxTop(int.MaxValue);
routeBuilder.MapODataServiceRoute("odata", string.Empty, odataBuilder.GetEdmModel());
});
Here is the UserDto:
public class UserDto
{
[Key] public Guid Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Token { get; set; }
}
When I post:
The action is resolved by the routing engine - but the parameter does not have the "Username" and "Password" values:
If I use the [FromBody] attribute on the parameter - the "userDto" parameter is null:
The schema seems correct:
<Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="Default">
<Action Name="authenticate">
<Parameter Name="userDto" Type="ExampleApi.Dto.UserDto"/>
</Action>
<EntityContainer Name="Container">
<EntitySet Name="Book" EntityType="ExampleApi.Dto.BookDto"/>
<EntitySet Name="User" EntityType="ExampleApi.Dto.UserDto"/>
<ActionImport Name="authenticate" Action="Default.authenticate"/>
</EntityContainer>
</Schema>
I have tried following this: Action Parameter Support
And even Microsofts version (albeit dated): Actions and Functions in OData
Been banging my head on this all day...
You could use simple WebApi attributes only to achieve authentication
public class UserController : ODataController
{
[AllowAnonymous]
[HttpPost("user/auth")]
public async Task<IActionResult> Authenticate([FromBody] UserDto userDto)
{
return Ok(userDto);
}
}

Discriminator column nhibernate

I have a discriminator column in the which i need to use for select insert and update. I need to update and select the Employee type. I have following code in the mapping xml file.
<discriminator column="EmployeeType" type="String"/>
<property name="EmployeeType" column="EmployeeType" update="false" insert="false" type="String" />
Please assist me
It is not possible to do what you want to do because changing the discriminator value would change the class type.
The only possible solutions are to:
Create a new instance of the class you want and copy the values from the other class instance.
Don't use inheritance and discriminators to store the type information. Use an enumerated property instead. This will allow you to change the type of the employee on the fly.
Update the discriminator using an SQL Update statement.
An example of why it does not make sense to this is the following. Say you have the following classes:
public abstract class Animal
{
public virtual Guid id { get; set; }
public virtual string AnimalType { get; set; }
...
}
public class Dog : Animal
{
public virtual bool loves_walkies { get; set; }
}
public class Cat : Animal
{
public virtual bool is_agile { get; set; }
}
With the mapping something like
<class name="Earth.Animal">
...
<discriminator column="AnimalType" type="String"/>
<property name="AnimalType" type="String" />
<subclass name="Earth.Cat" discriminator-value="Cat">
<property name="loves_walkies" />
</subclass>
<subclass name="Earth.Dog" discriminator-value="Dog">
<property name="is_agile" />
</subclass>
</class>
Now if NHibernate allowed you to do something like the following:
var dog = session.Get<Dog>(guid);
dog.AnimalType = "Cat";
session.SaveOrUpdate(dog);
What would the results be? Does NHibernate persist the loves_walkies property because the object is a Dog, but look the discriminator says it is a cat so it needs to persist the is_agile property.
To avoid this confusion NHibernate make the discriminator column read-only.
This question has been asked before, please see the links below for further reading:
https://groups.google.com/forum/?fromgroups=#!topic/nhusers/_Qqi4WCu2Bk
NHibernate - Changing sub-types

WCF: is there an attribute to make parameters in the OperationContract required?

I use [DataMember(IsRequired=true)] to make the DataContract properties required. There doesn't seem to be some IsRequired for the OperationContract parameters. How do I make them required and not allow null?
The parameter in of OperationContract appears to be optional in SoapUI tool. Though this must never be optional or null.
WCF Interface:
[OperationContract]
IsClientUpdateRequiredResult IsClientUpdateRequired(IsClientUpdateRequiredInput versie);
...
[DataContract]
public class IsClientUpdateRequiredInput
{
[DataMember(IsRequired=true)]
public string clientName { get; set; }
[DataMember(IsRequired = true, Order = 0)]
public int major { get; set; }
[DataMember(IsRequired = true, Order = 1)]
public int minor { get; set; }
[DataMember(IsRequired = true, Order = 2)]
public int build { get; set; }
[DataMember(IsRequired = true, Order = 3)]
public int revision { get; set; }
}
soapUI request template:
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:tem="http://tempuri.org/" xmlns:pir="http://schemas.datacontract.org/2004/07/PirIS.Web.WCF.InputClasses">
<soap:Header/>
<soap:Body>
<tem:IsClientUpdateRequired>
<!--Optional:-->
<tem:versie>
<pir:clientName>?</pir:clientName>
<pir:major>?</pir:major>
<pir:minor>?</pir:minor>
<pir:build>?</pir:build>
<pir:revision>?</pir:revision>
</tem:versie>
</tem:IsClientUpdateRequired>
</soap:Body>
</soap:Envelope>
Unfortunately it can't be done using default WCF. There exist a few workarounds:
A custom RequiredParametersBehavior attribute
Using the Validation Application Block from the Enterprise Library and associate a ruleset to your method
You can however implement a FaultContract and throw a fault when the input parameter is null.
No. Just like any regular method, you'll need to check whether reference type parameters have a value or are null.
Just apply your normal defensive programming patterns, checking reference types before accessing their properties.

WCF Data Services Reflection Provider Specify Association Types

I've been struggling to try and get Data Services to work with the new LightSwitch 2.0 OData Data Source.
Noticing that OData 3.0 is still not supported I had to fall back to 2.0 version.
It happens that my Data Context is nothing but wrapper over some xml serialized object to enable OData access.
This "magic" happens using the Reflection Provider and it works fine regarding all the CRUD operations.
The problems start when I try to use this service in LightSwitch, and realize that all my entity associations are wrong.
The situation I have is exactly the same as if you look at Microsoft's sample code.
So, using this data model:
[DataServiceKeyAttribute("OrderId")]
public class Order
{
public int OrderId { get; set; }
public string Customer { get; set; }
public IList<Item> Items { get; set; }
}
[DataServiceKeyAttribute("Product")]
public class Item
{
public string Product { get; set; }
public int Quantity { get; set; }
}
It's obvious the "one-to-many" relationship between Order [1 - *] Items.
But looking at the xml metadata of this service, the association is declared as "many-to-many":
<Association Name="Order_Items">
<End Type="WEBfactory.StreamInsight.Adapters.Carel.DataServices.Order" Multiplicity="*" Role="Order"/>
<End Type="WEBfactory.StreamInsight.Adapters.Carel.DataServices.Item" Multiplicity="*" Role="Items"/>
</Association>
Now, this doesn't really bother much when using a "Service Reference" client, but since LightSwitch doesn't support "many-to-many" relationships I always get a warning when trying consume this service and the relations are neither imported, nor possible to manually define.
Does anyone have a clue how to work enforce a relationship type using the Reflection Provider?
Thanks!!
The relationship between Order and Item in this case actually is many:many - if it were 1:many as you propose, an Item could only be in one Order.
That said, you can create the 1:* relationship by adding a corresponding property to the Item class:
[DataServiceKeyAttribute("Product")]
public class Item
{
public string Product { get; set; }
public int Quantity { get; set; }
public Order Order { get; set; }
}
That results in the following $metadata, which may or may not cause the same problem:
<Association Name="Order_Items">
<End Type="Scratch.Web.Order" Multiplicity="*" Role="Order"/>
<End Type="Scratch.Web.Item" Multiplicity="*" Role="Items"/>
</Association>
<Association Name="Item_Order">
<End Type="Scratch.Web.Order" Multiplicity="0..1" Role="Order"/>
<End Type="Scratch.Web.Item" Multiplicity="*" Role="Item"/>
</Association>
This is likely a limitation of the Reflection provider (I'll edit this answer if it turns out not to be), so the only workarounds today are to use either the EF provider or a custom provider.
I would make my WCF RIA Service work as described here:
How to Create a Many-to-Many Relationship
http://blogs.msdn.com/b/lightswitch/archive/2010/12/16/how-to-create-a-many-to-many-relationship-andy-kung.aspx

Resources