OData V4 REST using GUID primary key - rest

all! I am using OData v4 building REST services. My tables have a GUID primary key.
My GET and POST requests are working fine. But the PUT, PATCH, and DELETE requests fail with 404.
I am not sure what the url should look like. I've tried these in Fiddler, all getting the 404. I have googled this quite a bit with no luck.
http://localhost/ershubrest/AppVersions/guid'00000000-e90f-4938-b8f6-000000000000'
http://localhost/ershubrest/AppVersions/'00000000-e90f-4938-b8f6-000000000000'
http://localhost/ershubrest/AppVersions/00000000-e90f-4938-b8f6-000000000000
Here is the code for my controller...
using ERSHubRest.Models;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.OData;
using System.Web.OData.Query;
using System.Web.OData.Routing;
namespace ERSHubRest.controllers
{
[ODataRoutePrefix("AppVersions")]
public class AppVersionsController : ODataController
{
HubModel db = new HubModel();
private bool AppVersionsExists(System.Guid key)
{
return db.AppVersions.Any(p => p.AppVersionId == key);
}
// http GET for select queries
[ODataRoute]
[EnableQuery]
public IQueryable<AppVersions> Get()
{
return db.AppVersions;
}
[ODataRoute("({key})")]
[EnableQuery]
public IHttpActionResult Get([FromODataUri] System.Guid key)
{
IQueryable<AppVersions> result = db.AppVersions.Where(p => p.BusinessId == key);
if (result == null)
{
return NotFound();
}
return Ok(result);
}
// http POST for insert
[ODataRoute()]
[HttpPost]
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public async Task<IHttpActionResult> Post(AppVersions appVersions)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.AppVersions.Add(appVersions);
await db.SaveChangesAsync();
return Created(appVersions);
}
// http PUT and PATCH for updates
[ODataRoute()]
[HttpPatch]
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public async Task<IHttpActionResult> Patch([FromODataUri] System.Guid key, Delta<AppVersions> appVersions)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var entity = await db.AppVersions.FindAsync(key);
if (entity == null)
{
return NotFound();
}
appVersions.Patch(entity);
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!AppVersionsExists(key) )
{
return NotFound();
}
else
{
throw;
}
}
return Updated(entity);
}
[ODataRoute()]
[HttpPut]
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public async Task<IHttpActionResult> Put([FromODataUri] System.Guid key, AppVersions update)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if ( ! key.Equals( update.BusinessId ))
{
return BadRequest();
}
if (!AppVersionsExists(key))
{
return BadRequest();
}
db.Entry(update).State = EntityState.Modified;
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if ( ! AppVersionsExists(key))
{
return NotFound();
}
else
{
throw;
}
}
return Updated(update);
}
// last is Delete
[ODataRoute()]
[HttpDelete]
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public async Task<IHttpActionResult> Delete([FromODataUri] System.Guid key)
{
var appVersions = await db.AppVersions.FindAsync(key);
if (appVersions == null)
{
return NotFound();
}
db.AppVersions.Remove(appVersions);
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.NoContent);
}
// clean up
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
}
}

The request URL for PATCH, PUT and DELETE should be:
http://localhost/ershubrest/AppVersions(00000000-e90f-4938-b8f6-000000000000)
OData is using parenthesizes for addressing single entities using keys.
For more URL conventions, the OData V4 URL convention spec can be referred to: http://docs.oasis-open.org/odata/odata/v4.0/os/part2-url-conventions/odata-v4.0-os-part2-url-conventions.html

Try this:
http://localhost/ershubrest/AppVersions(guid'00000000-e90f-4938-b8f6-000000000000')
That should work!!

Odata V1-3 : http://localhost/ershubrest/AppVersions(guid'00000000-e90f-4938-b8f6-000000000000')
Odata V4 : http://localhost/ershubrest/AppVersions(00000000-e90f-4938-b8f6-000000000000)
I've tested Odata for 2 days
I ensure that !

Related

Error 500 "Internal Server Error" between Flutter App and my BackEnd

I have an error and i tired to look up it and i still not find it.
Scenario: I made my Backend in C# with VS, the FrontEnd is with Flutter.
all my backend works perfectly with all my Flutter App, but there is one problem with only one Action.
When i try to "Save, Update or Delete " a record, it appears the next message in my app, when i put a revision point in my Flutter code in the action to save/delete or update, apperars the CodeError 500 and "Internal Server Error".
The DB is in Azure, and i allready erase and create it twice, and the error persist.
the error is only with this code, the other Ssave/update or delete action.
attach the Images of my code:
Code of The Backend
using System.ComponentModel.DataAnnotations;
namespace Vehicles.API.Models.Request
{
public class HistoryRequest
{
public int Id { get; set; }
[Required(ErrorMessage = "El campo {0} es obligatorio")]
public int VehicleId { get; set; }
[Required(ErrorMessage = "El campo {0} es obligatorio")]
public int Mileage { get; set; }
[Required(ErrorMessage = "El campo {0} es obligatorio")]
public string Remarks { get; set; }
}
}
Other Page, the API Contoller
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Vehicles.API.Data;
using Vehicles.API.Data.Entities;
using Vehicles.API.Models.Request;
namespace Vehicles.API.Controllers.API
{
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Route("api/[controller]")]
[ApiController]
public class HistoriesController : ControllerBase
{
private readonly DataContext _context;
private readonly IUserhelper _userhelper;
public HistoriesController(DataContext context, IUserhelper userhelper)
{
_context = context;
_userhelper = userhelper;
}
[HttpGet("{id}")]
public async Task<ActionResult<History>> GetHistory(int id)
{
History history = await _context.Histories
.Include(x => x.Details)
.ThenInclude(x => x.Procedure)
.FirstOrDefaultAsync(x => x.Id == id);
if (history == null)
{
return NotFound();
}
return history;
}
[HttpPost]
public async Task<ActionResult<History>> PostHistory(HistoryRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Vehicle vehicle = await _context.Vehicles.FindAsync(request.VehicleId);
if (vehicle == null)
{
return BadRequest("El Vehiculo no existe");
}
string email = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value;
User user = await _userhelper.GetUserAsync(email);
if (user == null)
{
return BadRequest("La usuario no existe");
}
History history = new()
{
Date = DateTime.UtcNow,
Details= new List<Detail>(),
Mileage=request.Mileage,
Remarks=request.Remarks,
User=user,
Vehicle=vehicle,
};
_context.Histories.Add(history);
await _context.SaveChangesAsync();
return Ok(history);
}
[HttpPut("{id}")]
public async Task<IActionResult> PutHistory(int id, HistoryRequest request)
{
if (id != request.Id)
{
return BadRequest();
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
History history = await _context.Histories.FindAsync(request.Id);
if (history == null)
{
return BadRequest("La historia no existe");
}
history.Mileage = request.Mileage;
history.Remarks= request.Remarks;
_context.Histories.Update(history);
await _context.SaveChangesAsync();
return NoContent();
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteHistory(int id)
{
History history = await _context.Histories
.Include(x => x.Details)
.FirstOrDefaultAsync(x => x.Id == id);
if (history == null)
{
return NotFound();
}
_context.Histories.Remove(history);
await _context.SaveChangesAsync();
return NoContent();
}
}
}
Code in VC Flutter:
Response response = await ApiHelper.delete(
'/api/Histories/', widget.history.id.toString(), widget.token);
setState(() {
_showLoader = false;
});
if (!response.isSuccess) {
await showAlertDialog(
context: context,
title: 'Error',
message: response.message,
actions: <AlertDialogAction>[
AlertDialogAction(key: null, label: 'Aceptar'),
]);
return;
}
Navigator.pop(context, 'yes');
}
And the Code of the API code to conect with the backend:
static Future<Response> delete(
String controller, String id, Token token) async {
if (!_validToken(token)) {
return Response(
isSuccess: false,
message:
'Sus Credenciales se han vencido, favor cierre sesion y vuelva a ingresar nuevamente al sistema.');
}
var url = Uri.parse('${Constans.apiUrl}$controller$id');
var response = await http.delete(
url,
headers: {
'content-type': 'application/json',
'accept': 'application/json',
'authorization': 'bearer ${token.token}',
},
);
if (response.statusCode >= 400) {
return Response(isSuccess: false, message: response.body);
}
return Response(isSuccess: true);
}

How to perform edit action in ASP.net Core?

How to perform edit action in ASP.net Core? I have the following code for product detail view action i need help on creating action for edit page
//IProduct interface
namespace Proj.Core.App.Common.Product
{ public interface IProductService
{
Task<IList<ProductDTO>> GetProducts();
}
}
//Product Controller
public class ProductController : Controller
{
public IProductService ProductService { get; }
public ProductController(IProductService ProductService)
{
ProductService = ProductService;
}
//DetailAction
[HttpGet()]
public async Task<IActionResult> Detail(int id)
{
var ProductList= (await ProductService.GetProducts()).ToList();
var project = ProductList.FirstOrDefault(a => a.ID == id);
#ViewBag.Product_Code = product.productCode;
#ViewBag.Product_Name = product.productName;
return View();
}
}
how can i create action for Edit page action?
I left a comment that the question really needs more information to answer properly. However, maybe the stuff below might help to get you started.
Here is an implementation using the methods you already have.
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var productList = (await ProductService.GetProducts()).ToList();
var product = productList.FirstOrDefault(a => a.ID == id);
if (product == null)
{
return NotFound();
}
return View(product);
}
This is what typical entity framework implementation looks like. Extract what information you can from it.
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var product = await _context.Products.FindAsync(id);
if (product == null)
{
return NotFound();
}
return View(applications);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(Product product)
{
if (ModelState.IsValid)
{
try
{
_context.Update(product);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ProductExists(product.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(applications);
}
private bool ProductExists(int id)
{
return _context.Products.Any(e => e.Id == id);
}
Hope that helps.
Happy coding!!!
//DetailAction
[HttpPut()]
public async Task<IActionResult> Put(Product model)
{
..call your service or ...
return View();
}

Pass ID once to a controller and have all controller methods remember boolean check

I just created a simple web API using .NetCore 2.2 and Entity Framework.
I added a bit of security, by passing in a userID to each controller that the user accesses.
But I noticed that it starts getting messy when I have to add the userID to every controller in my app and the run my user check to make sure the user can access that content.
Below you'll see an example of what I mean.
I'm wondering, is there a way to add it once and then have every controller check for it?
Thanks!
[Route("api/[controller]")]
[ApiController]
public class EngineController : ControllerBase
{
private readonly engineMaker_Context _context;
public EngineController(engineMaker_Context context)
{
_context = context;
}
// GET: api/Engine
[HttpGet("{userID}")]
public async Task<ActionResult<IEnumerable<Engine>>> GetEngine(string userID)
{
if(!CanAccessContent(userID))
{
return Unauthorized();
}
return await _context.Engine.ToListAsync();
}
// GET: api/Engine/123/5
[HttpGet("{userID}/{id}")]
public async Task<ActionResult<Engine>> GetEngine(string userID, string id)
{
if(!CanAccessContent(userID))
{
return Unauthorized();
}
var engine = await _context.Engine.FindAsync(id);
if (engine == null)
{
return NotFound();
}
return engine;
}
// PUT: api/Engine/123/5
[HttpPut("{userID}/{id}")]
public async Task<IActionResult> PutEngine(string userID, string id, Engine engine)
{
if(!CanAccessContent(userID))
{
return Unauthorized();
}
if (id != engine.ObjectId)
{
return BadRequest();
}
_context.Entry(engine).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!EngineExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
private bool CanAccessContent(string userID)
{
return _context.AllowedUsers.Any(e => e.UserId == userID);
}
}
You could try IAsyncAuthorizationFilter to check the userID.
IAsyncAuthorizationFilter
public class UserIdFilter : IAsyncAuthorizationFilter
{
public Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
var dbContext = context.HttpContext.RequestServices.GetRequiredService<ApplicationDbContext>();
var userId = context.RouteData.Values["userID"] as string;
if (!dbContext.Users.Any(u => u.Email == userId))
{
context.Result = new UnauthorizedResult();
}
return Task.CompletedTask;
}
}
Regiter UserIdFilter for all action.
services.AddMvc(options =>
{
options.Filters.Add(typeof(UserIdFilter));
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

InvokeApiAsync<HttpResponseMessage> returns null

Can someone explain my why that client (Xamarin.Forms PCL) call returns null?
HttpResponseMessage response = await OfflineSyncStoreManager.Instance.MobileAppClient.InvokeApiAsync<HttpResponseMessage>("ResetTruckAuftragWorkflow");
response is null. When I execute that in a console app it returns the
valid http response.
I use the latest stable ZUMO nugets in client and backend. There is my ZUMO backend code:
[Authorize]
[MobileAppController]
public class ResetTruckAuftragWorkflowController : ApiController
{
private readonly RcsMobileContext _rcsMobileContext;
private readonly TruckFahrerInfo _truckFahrerInfo;
public ResetTruckAuftragWorkflowController()
{
_rcsMobileContext = new RcsMobileContext();
_truckFahrerInfo = new TruckFahrerInfo(this.User as ClaimsPrincipal);
}
// POST api/ResetTruckAuftragWorkflow
[HttpPost]
public async Task<IHttpActionResult> PostAsync()
{
if (ModelState.IsValid)
{
using (var transaction = _rcsMobileContext.Database.BeginTransaction())
{
try
{
var truckAuftragList = _rcsMobileContext.TruckAuftrags.PerUserFilter(_truckFahrerInfo.FahrerId);
var truckAppIds = truckAuftragList?.Select(ta => ta.TruckAppId).ToArray();
if (truckAppIds != null)
{
foreach (var truckAppId in truckAppIds)
{
await _rcsMobileContext.Database.ExecuteSqlCommandAsync(_tawQueryTaskStatus10, truckAppId);
await _rcsMobileContext.Database.ExecuteSqlCommandAsync(_tawQueryTaskStatus5, truckAppId);
await _rcsMobileContext.Database.ExecuteSqlCommandAsync(_talQuery, truckAppId);
await _rcsMobileContext.Database.ExecuteSqlCommandAsync(_taQuery, truckAppId);
}
}
await _rcsMobileContext.Database.ExecuteSqlCommandAsync(_taQuery, _truckFahrerInfo.FahrerId);
transaction.Commit();
}
catch (Exception e)
{
transaction.Rollback();
return BadRequest($"Transaction failed: {e}");
}
}
return Ok();
}
else
{
return BadRequest(ModelState);
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_rcsMobileContext.Dispose();
}
base.Dispose(disposing);
}
}
thanks
Eric
InvokeApiAsync decodes the body that is returned and de-serializes the JSON into type T. You should not use HttpResponseMessage for this purpose as it is not serializable.
If you don't care about the body, use the non-generic form of InvokeApiAsync.

Multiple OData entity GET methods

I've used OData with entity framework to create a simple web service. Everything works fine. I just need to know how to add additional GET commands such as "GetByCategory" or "GetByDate". These will use different SQL views to return complex filtered results.
** WebApiConfig.cs *
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
using Microsoft.Owin.Security.OAuth;
using Newtonsoft.Json.Serialization;
using System.Web.Http.OData.Builder;
using System.Web.Http.OData.Extensions;
using NRHSData.Models;
namespace NRHSData
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
config.AddODataQueryFilter();
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.Namespace = "NRHSData";
builder.EntitySet<employee>("employees");
builder.EntitySet<vw_employee_listing>("vw_employee_listings");
builder.EntitySet<physician>("physicians");
builder.EntitySet<vw_physician_listing>("vw_physician_listings");
builder.EntitySet<employeedepartment>("employeedepartments");
builder.EntitySet<vw_employeedepartment_listing>("vw_employeedepartment_listings");
builder.EntitySet<news>("news");
builder.EntitySet<vw_news_listing>("vw_news_listings");
builder.EntitySet<vw_news_current>("vw_news_current");
config.Routes.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());
}
}
}
** newsController.cs *
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.ModelBinding;
using System.Web.Http.OData;
using System.Web.Http.OData.Routing;
using NRHSData.Models;
namespace NRHSData.Controllers
{
[Authorize]
public class newsController : ODataController
{
private ENTDATAEntities db = new ENTDATAEntities();
// GET: odata/news
[EnableQuery]
public IQueryable<vw_news_listing> Getnews()
{
return db.vw_news_listings;
}
// GET: odata/news(5)
[EnableQuery]
public SingleResult<vw_news_listing> Getnews([FromODataUri] int key)
{
return SingleResult.Create(db.vw_news_listings.Where(news => news.newsid == key));
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
private bool newsExists(int key)
{
return db.news.Count(e => e.newsid == key) > 0;
}
// PUT: odata/news(5)
public async Task<IHttpActionResult> Put([FromODataUri] int key, Delta<news> patch)
{
Validate(patch.GetEntity());
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
news news = await db.news.FindAsync(key);
if (news == null)
{
return NotFound();
}
patch.Put(news);
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!newsExists(key))
{
return NotFound();
}
else
{
throw;
}
}
return Updated(news);
}
// POST: odata/news
public async Task<IHttpActionResult> Post(news news)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.news.Add(news);
await db.SaveChangesAsync();
return Created(news);
}
// PATCH: odata/news(5)
[AcceptVerbs("PATCH", "MERGE")]
public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<news> patch)
{
Validate(patch.GetEntity());
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
news news = await db.news.FindAsync(key);
if (news == null)
{
return NotFound();
}
patch.Patch(news);
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!newsExists(key))
{
return NotFound();
}
else
{
throw;
}
}
return Updated(news);
}
// DELETE: odata/news(5)
public async Task<IHttpActionResult> Delete([FromODataUri] int key)
{
news news = await db.news.FindAsync(key);
if (news == null)
{
return NotFound();
}
db.news.Remove(news);
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.NoContent);
}
}
}
I assume you are trying to address such requests:
~/News/GetByDate()
There are 2 options:
Using OData Actions. Add an action named GetByDate and issue the Request through 'POST ~/News/GetByDate()' But it does NOT align with the protocol. An Action should have some side effect. So the second option comes.
Using OData Functions, which must not have side effect. To implement this, you need to upgrade to the new bits: System.Web.OData.dll. here is a sample about functions: https://aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/OData/v4/ODataFunctionSample/ .