Querying data with Web API and entity framework through odata - c#

I'm trying to make odata rest-based service calls through a URI.
I have an ADO.NET Entity data model that is mapped to one of the tables on the SQL back end. Which created the necessary edmx file under my "Models" folder
I then created a controller labeled spcontrol using the "Web API 2 Odata Controller with actions, using Entity Frameowrk" option. I then set the model class and Data context class and the visual studio scaffolding did the rest. The first few lines of the controller look like this...
public class spcontrolController : ODataController
{
private Entities db = new Entities();
// GET odata/spcontrol
[Queryable]
public IQueryable<Database_table_Name> Getspcontrol()
{
return db.Database_table_Name;
}
// GET odata/spcontrol(5)
[Queryable]
public SingleResult<Database_table_Name> GetDatabase_table_Name([FromODataUri] int key)
{
return SingleResult.Create(db.Database_table_Name.Where(Database_table_Name=> Database_table_Name.ID == key));
}
and then it goes on for the PUT, POST, PATCH, DELETE methods. In my WebApiConfig.cs file, I set the following lines
using System.Web.Http.OData.Builder;
using resttest.Models;
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Database_table_Name>("spcontrol");
config.Routes.MapODataRoute("odata", "odata", builder.GetEdmModel());
Finally, I'm trying to query the table with odata. So my url looks something along the lines of
http://testsite:8012/odata/spcontrol?$top=5
However, this call does not return any data, but only returns the following text
{
"odata.metadata":"http://localhost:33421/odata/$metadata#spcontrol","value":[
]
}
How can I get the actual values within the SQL table? What am I doing wrong?

Looks like it is working fine - you just have no data to return so array in response payload is empty. Add some data and hit the endpoint again.
Run a profiler on your database to see what commands are being sent from your service to check correct tables are being used etc

Related

Routing controllers with the same name in multiple OData contexts in the same app

I am using .Net Core 2.2 as my underlying framework here, so this information might not be inkeeping with how things are currently done (if so please, I welcome the feedback on how to put this right).
I have an Api App that initializes multiple OData contexts that happen to have some crossover in entity set names and thus controller names.
In this case I have a "Core" OData context + model and a "Members" context + model.
the net result is that both of these OData models contains a Users entity set with controllers looking like this ...
[ODataRoutePrefix("Members/User")]
public class UserController : MembersEntityODataController<Members.User> { }
[ODataRoutePrefix("Core/User")]
public class UserController : CoreEntityODataController<Core.User> { }
... they essentially do the same job but refer to entities stored in the same table but in different databases.
I can't seem to for the life of me figure out how to initialize / declare these controllers such that the routing actually works and instead all requests for both ~/Core/User and ~/Members/User result in a members user controller being passed on to handle the request.
My understanding is that this is the exact scenario that the ODataRoutePrefix attribute was built to solve (amongst others) and it doesn't seem to help here.
to make the process of adding new OData models to my API easier I wrapped up the model construction in my own model building but the net result is a core call to use odata during startup which looks like this ...
app.UseMvc(routeBuilder =>
{
routeBuilder.EnableDependencyInjection();
routeBuilder.MapRoute("default", "{controller=Home}/{action=Get}");
});
var builders = new ODataModelBuilder[] {
new MembersModelBuilder(),
new CoreModelBuilder(),
new B2BModelBuilder(),
new SearchModelBuilder()
};
foreach (var builder in builders)
{
var model = builder.Build();
app.UseOData(model.Context + "Api", builder.GetType().Name.Replace("ModelBuilder", ""), model.EDMModel);
}
Do I need to do something special here to make routing work as intended?
It does seem that this "type of scenario" has been considered before as I'm seeing things like this ...
https://github.com/OData/WebApi/issues/1494
... which talks about API versions ... my case isn't quite this, but it's close enough that I figure the same parts of the framework logic should apply (somewhat).
#xuzhg talks about the solution being to apply the ODataRoute Attribute on the Actions ...
[ODataRoute("Sensors", RouteName = "ODataRouteV1")]
public IActionResult Get()
[ODataRoute("Sensors", RouteName = "ODataRouteV2")]
public IActionResult Get()
... I need to do presumably be able to do the same thing at the controller level but alas this attribute cannot be used on classes only methods.
Obviously i'm sourcing my understanding from this ...
https://learn.microsoft.com/en-us/odata/webapi/attribute-routing
... which talks about using the ODataRoutePrefix attribute to apply context to the routing engine for when a controller should be selected.
Have I hit an edge case here ?

Returning related entities from functions in OData Web Api

I've created an OData v4 endpoint using ASP.NET Web API 2.2 with 2 entities:
And i made function in my controller, that returns entity with related entity by using Include in LINQ:
[HttpGet]
[ODataRoute("SalaryNaryadWork/TechOrder.Functions.GetNaryadSalaryWork(pNaryadID={pNaryadID})")]
public IHttpActionResult GetNaryadSalaryWork(int pNaryadID)
{
var tmp =_ctx.SALARY_NARYAD_WORK.Include(s => s.SALARY_WORK_LIST).Where(sw => sw.NARYAD_ID == pNaryadID).ToList();
return Ok(tmp);
}
Function configuration code is:
var GetNaryadSalaryWork = builder.EntityType<SALARY_NARYAD_WORK>().Collection.Function("GetNaryadSalaryWork");
GetNaryadSalaryWork.Parameter<int>("pNaryadID");
GetNaryadSalaryWork.ReturnsCollectionFromEntitySet<SALARY_NARYAD_WORK>("SalaryNaryadWork");
GetNaryadSalaryWorkList.Namespace = "TechOrder.Functions";
But when I call this function by link ../SalaryNaryadWork/TechOrder.Functions.GetNaryadSalaryWork(pNaryadID=1) I get only my entity without related entity:
Despite the fact that function returns related entity either (I saw that in variable tmp when debug function in visual studio)
So my question, is it possible to fix that and get related entity exactly by function without using expand option on Get() controller function?

Generic Remote Validations in MVC 5

I am developing an MVC 5 app using EF 6 database first approach. I have a certain validation which is required for fields in many of my models. I am using remote validation for validating them. Since it was being used in a lot of models so I am trying to go for a generic method.
For that matter I made an Interface named IEntity which includes all properties being used in my models. Then I did the following for my Validation method:
[HttpPost]
public JsonResult UniqueCheck<T>(T code) where T : class, IEntity
{
MyEntities db = new MyEntities();
if (db.Set<T>().Any(x=>x.S1 == code.S1))
{
return Json("Already Exists!");
}
else
{
return Json(true);
}
}
And following is how I am calling the validation on properties in models:
[Remote("UniqueCheck", "Rules", HttpMethod = "POST")]
public string S1 { get; set; }
But the problem is that the validation is not working and when I checked in the browser console I got that the validation is actually going into the method but there was a 500(Internal Server Error) returned.
I know that the problem is with T written with method name because when I removed the generics and hardcoded my model name, it works fine.
I only want to use MVC's remote validation and I would be very happy to get this generic method working because otherwise it would be a copy/paste on a lot of locations.

OData v4 custom route using DTO problems

I have setup an OData v4 controller from an EF 6 model. All works fine when using the generated code.
On some objects that will be referenced in drop downs I want to limit the data sent so DTOs seem like the best approach. So I now have a controller the works fine with the normal object but as soon as I use a DTO on a custom route I run into trouble.
I've used the code below and it generated the DTO collection properly but when it returns the data I end up on the client with a 406 Not Acceptable status.
Any suggestions about what is going wrong?
As a side note, is my strategy of wanting to provide a full and a cut down object the best way to do it (so far as standards go)?
// GET: odata/AMO_GeneralStateTypes(5)/AMO_GeneralStates_Basic
[EnableQuery]
[Route("odata/AMO_GeneralStateTypes({key})/AMO_GeneralStates_Basic")]
public IQueryable<AMO_GeneralStatesDTO> GetAMO_GeneralStates_Basic([FromODataUri] int key)
{
return db.AMO_GeneralStateTypes
.Where(m => m.StateTypeId == key)
.SelectMany(m => m.AMO_GeneralStates.Select(n => new AMO_GeneralStatesDTO()
{
StateId = n.StateId,
StateGuid = n.StateGuid,
ParentStateId = n.ParentStateId,
State = n.State,
TypeId = n.TypeId,
SortOrder = n.SortOrder,
Enabled = n.Enabled
}));
}
For 406 Not Acceptable, please make sure the return type is defined in the finally Edm Model. That is, AMO_GeneralStatesDTO is type that Edm model can serialize.
BTW, For Web API OData,
[Route("odata/AMO_GeneralStateTypes({key})/AMO_GeneralStates_Basic")]
should be
[ODataRoute("odata/AMO_GeneralStateTypes({key})/AMO_GeneralStates_Basic")]
I have met some problems like this in odata webapi for dto
here is my solution: you need to register your entitytype for odata or it will come a 406 error
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntityType<YourDTOType>();
Hope it's helpful

partial update with asp.net web api

I created a simple todo list application in asp.net mvc 3 with web api and dbContext. ( with backbone and requirejs for the client )
Everything works fine, but i am sort of bothered with the fact that i have to sent the entire model to the server if i check or uncheck a todo item as done.
I would like to only sent the "done" field when submitting data.
I should mention that i'm also using the JsonNetFormatter to use JSON.NET as the default Serializer ( explained here: http://blogs.msdn.com/b/henrikn/archive/2012/02/18/using-json-net-with-asp-net-web-api.aspx ).
Currently this is my api controller method to update the model
public HttpResponseMessage Put(Todo todo)
{
_db.Entry(todo).State = EntityState.Modified;
_db.SaveChanges();
return new HttpResponseMessage(HttpStatusCode.NoContent);
}
It takes this as json data
{"content":"Pick up milk","done":false,"id":10}
Off course this works, but it is updating the entire model, it should only update 1 field.
I can achieve to only send the changed fields to the server from the browser, but i am not sure what the web api method should look like.
I was thinking about doing something with FormCollection but this doesn't seem to work with the web api as it appears to be trying to serialize the submitted formvalues directly to FormCollection type, I get this error.
Cannot deserialize JSON object (i.e. {"name":"value"}) into type 'System.Web.Mvc.FormCollection'.
How can i send a partial update for 1 or more fields from a model to my web api ?
I only want to send the updated fields to the server, and from there only update those fields to the database. I certainly do not want to query the database before updating.
One approach would be to use a tool called Automapper and configure it so that null values don't overwrite existing ones when mapping Todo objects. For example:
Mapper.CreateMap<Todo,Todo>()
.ForMember(d => d.Id, o => o.Ignore())
.ForAllMembers(mo => mo.Condition(cond => !cond.IsSourceValueNull));
Then you would just have to map the received object value to the existing one, like this:
Mapper.Map(todo, item);
Another suggestion would be to use PATCH instead of PUT which is more appropriate to partial updates of resources according to REST.
You need to query the original object from the database, set its properties & call the _db.SaveChange()
public HttpResponseMessage Put(Todo todo){
var item = _db.Todo.First(i => i.id = todo.id);
item.Content = todo.Content;
item.Done = todo.Done;
_db.SaveChanges();
return new HttpResponseMessage<Todo>(HttpStatusCode.Accepted);
}
Ref.: http://msdn.microsoft.com/en-us/library/dd456854.aspx

Categories