How to implement an identical POST like the regular PUT in an odata controller to update an entity - asp.net-web-api2

Given the following typical implementation of an ODataController's PUT method, how would I make the exact same method ALSO be available as a POST?
I am developing an OData end-point that will be called from an external system that I have no control over. It appears that that system implements the Update semantics (to tell my system to update an entity) wrongly by sending a POST with a uri key instead of using a PUT.
public async Task<IHttpActionResult> Put([FromODataUri] int key, Product update)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (key != update.Id)
{
return BadRequest();
}
db.Entry(update).State = EntityState.Modified;
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ProductExists(key))
{
return NotFound();
}
else
{
throw;
}
}
return Updated(update);
}
My first guess was to annotate the method with [AcceptVerbs("PUT", "POST")] to make the same exact method implementation be available as a POST, but that doesn't work. It's probably that the ODataConventionModelBuilder default setup doesn't know about this...
Ideally I'd like to keep the standards based PUT and the regular POST for inserts, but add a special post that is identical to the put but differs only in the verb.
Thanks

After finding some not so evident documentation on salesforce.com on odata endpoint implementation for External Data Source/External Objects, it became evident to me that salesforce.com tries to call a POST for Update semantics on the external object but also adds the X-HTTP-METHOD set as PATCH.
So, the solution was to implement the following class:
public class MethodOverrideHandler : DelegatingHandler
{
readonly string[] _methods = { "DELETE", "HEAD", "PUT", "PATCH", "MERGE" };
const string _header1 = "X-HTTP-Method-Override";
const string _header2 = "X-HTTP-Method";//salesforce special behavior???
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
// Check for HTTP POST with the X-HTTP-Method-Override header.
if (request.Method == HttpMethod.Post && request.Headers.Contains(_header1))
{
// Check if the header value is in our methods list.
var method = request.Headers.GetValues(_header1).FirstOrDefault();
if (_methods.Contains(method, StringComparer.InvariantCultureIgnoreCase))
{
// Change the request method.
request.Method = new HttpMethod(method);
}
}
else if (request.Method == HttpMethod.Post && request.Headers.Contains(_header2))
{
// Check if the header value is in our methods list.
var method = request.Headers.GetValues(_header2).FirstOrDefault();
if (_methods.Contains(method, StringComparer.InvariantCultureIgnoreCase))
{
// Change the request method.
request.Method = new HttpMethod(method);
}
}
return base.SendAsync(request, cancellationToken);
}
}
and register it in WebApiConfig.Register(HttpConfiguration config) as such:
config.MessageHandlers.Add(new MethodOverrideHandler());
Now, the non-odata compliant POST for salesforce update operations on the External Object will get delegated to the standards compliant odata implementation (in the ODataController) of PUT method I originally posted.
I hope that this helps someone in the future...

My approach would be to throw some more logic into the method to check and see if a record already exists in the database using update.Id then checking whether or not the data is null.
public async Task<IHttpActionResult> Put([FromODataUri] int key, Product update)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
//might have to get rid of this condition for the sake of new entries
//if (key != update.Id)
//{
//return BadRequest();
//}
try
{
//not sure what the name of your table is so I'm going to call it ProductTable
var foo = db.ProductTable.Where(p => p.Id == update.Id).FirstOrDefault();
if(foo == null)
{
db.Entry(update).State = EntityState.Added;
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.Accepted);
}
else
{
db.Entry(update).State = EntityState.Modified;
await db.SaveChangesAsync();
return Updated(update);
}
}
catch (DbUpdateConcurrencyException ex)
{
if (!ProductExists(key))
{
return NotFound();
}
else
{
throw new DbUpdateConcurrencyException(ex.Message);
}
}
}
EDIT
Just noticed the ProductExists method... I would take that out of the catch block and throw it into the try
//for Post, pass in a 0 for key's argument
public async Task<IHttpActionResult> Put([FromODataUri] int key, Product update)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
//might have to get rid of this condition for the sake of new entries
//if (key != update.Id)
//{
//return BadRequest();
//}
try
{
if (!ProductExists(key))
{
db.Entry(update).State = EntityState.Added;
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.Accepted);
}
else
{
db.Entry(update).State = EntityState.Modified;
await db.SaveChangesAsync();
return Updated(update);
}
}
catch (DbUpdateConcurrencyException ex)
{
throw new DbUpdateConcurrencyException(ex.Message);
}
}

Related

Update existing table row in the same method that uses HttpPost to add a new entry

I have a web game that uses .NetCore Entity Framework.
I have one method that uses HttpPost to create a new Monster in the database.
This method also needs to add a foreign key, the new MonsterId, to an existing Dungeon in the table called DungeonList.
I got the part where the method creates a new Monster correctly.
However I'm not sure how to insert the new MonsterId into the appropriate Dungeon of DungeonList.
I'm not exactly sure how to get the Id of the Dungeon.
Should I pass in the DungeonId from the frontend?
I'm really confused.
Here is what I have so far but I am not sure where to go from here.
I'd love some advice...thank you!
[HttpPost]
public async Task<ActionResult<MonsterList>> PostMonsterList(MonsterList monsterList)
{
monsterList.MonsterId = Guid.NewGuid();
_context.MonsterList.Add(monsterList);
var dungeonListRef = new DungeonList();
if(dungeonListRef.MonsterId == null)
{
// ????
}
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException)
{
if (MonsterListExists(monsterList.MonsterId))
{
return Conflict();
}
else
{
throw;
}
}
_context.DungeonList.Add(dungeonListRef);
return CreatedAtAction("GetMonsterList", new { id = monsterList.MonsterId }, monsterList);
}
Add Dungeon drop down list in your "Add new Monster" page. Send drop down list's dungeonID to PostMonsterList function.
[HttpPost]
public async Task<ActionResult<MonsterList>> PostMonsterList(Guid dungeonId, MonsterList monsterList)
{
Guid newMonsterId = Guid.NewGuid();
monsterList.MonsterId = newMonsterId;
_context.MonsterList.Add(monsterList);
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException)
{
if (MonsterListExists(monsterList.MonsterId))
{
return Conflict();
}
else
{
throw;
}
}
var dungeonList = _context.DungeonList.Where(x => x.DungeonId == dungeonId).FirstOrDefault();
dungeonList.MonsterId = newMonsterId;
_context.DungeonList.Update(dungeonList);
await _context.SaveChangesAsync();
return CreatedAtAction("GetMonsterList", new { id = monsterList.MonsterId }, monsterList);
}

Clear API errors [UWP]

I have a problem with my API, the GET, POST, DELETE and PUT work perfectly. but if for some reason at the time of making a PUT or a POST I generate errors (404, 400, 500, etc) and it is normal that in some times for any query I generate errors ... the problem is that the next Once I go back to do a POST or PUT (being aware that it is ok) the error is stored and it does not let me make the new query ... I do not know what to do or what is wrong. but the GET is still working
THIS IS THE CONTROLLER CODE:
[HttpGet("{id}")] //GET
public async Task<IActionResult> Get(long id)
{
if (id == 0)
return BadRequest();
var tercero = await repository.GetAsync(id);
if (tercero == null)
return NotFound();
else
return Ok(tercero);
}
[HttpPost] //POST
public async Task<IActionResult> Post([FromBody]Tercero tercero)
{
try
{
return Ok(await repository.InsertAsync(tercero));
}
catch(Exception e)
{
return BadRequest(e.ToString());
}
}
[HttpPut("{id}")] //PUT
public async Task<IActionResult> Put([FromBody]Tercero tercero, long Id)
{
if (tercero.Id != Id)
return BadRequest("No hay correlacion entre el id y el objeto");
try
{
return Ok(await repository.UpdateAsync(tercero, Id));
}
catch (Exception e)
{
return BadRequest(e.ToString());
}
}
[HttpDelete("{id}")] //DELETE
public async Task<IActionResult> Delete(long id)
{
try
{
await repository.DeleteAsync(id);
return Ok();
}
catch (Exception e)
{
return BadRequest(e.ToString());
}
}
I have other controllers and with all the same thing happens. I do not know if he finds something to clean up the error
THIS IS WHAT HAPPENS
STEP 1:
I do a POST with a new User
It returns the model of the query with a 200 OK
STEP 2:
I generate an error giving the same code in one of the fields what the database does not allow me.
It shows me the error with a 400 Bad Request
STEP 3: I realize my mistake and I decide to correct it to make a correct POST
It gives me the same error
WHY? How can i fix it?

OData V4 return updated entity on patch

I know that I can set the header like this
headers: {
'Prefer': 'return=representation'
}
But I would like to ask if it's possible to make it the default behaviour on my controller.
public IHttpActionResult Patch(int key, Delta<T> delta)
{
Validate(delta.GetEntity());
if (!ModelState.IsValid)
return BadRequest(ModelState);
var entity = Repository.GetByKey(key);
if (entity == null)
return NotFound();
if (!AuthenticationService.HasWriteAccess(CurentUser, entity))
return Unauthorized();
try
{
delta.Patch(entity);
Repository.Save();
}
catch (Exception e)
{
return InternalServerError(e);
}
return Updated(entity);
}
Is there an attribute I can set or can I configure it in startup?
Try this. Change your return to this.
var updatedODataResult = Updated(entity);
updatedODataResult.Request.Headers.Add("Prefer", "return=representation");
return updatedODataResult;

MVC aynchronous method

I am working on a MVC project that submits a request via a third party.
In my controller, I have a SubmitClaims() action that receive ajax request and then calls RunAsync(). RunAsync submits a request by using HttpClient.
I am not sure if I did a right thing here.
Also I have two version of SubmitClaims(), both work. But I don't know which version is better.
version 1
[HttpPost]
public async Task<string> SubmitClaims()
{
string result = "";
result = await RunAsync();
return result;
}
version 2 learn from Cannot implicitly convert type 'string' to 'System.Threading.Tasks.Task<string>'
[HttpPost]
public async Task<string> SubmitClaims()
{
return await Task.Run(() =>
{
return RunAsync();
});
}
static async Task<string> RunAsync()
{
string result = "Failed.";
using (var client = new HttpClient())
{
try
{
client.BaseAddress = new Uri("http://peter:8001/internal/uickpost");
client.DefaultRequestHeaders.Add("contenttype", "application/xml");
client.DefaultRequestHeaders.Add("hiconline.protocol.content.role", "REQUEST");
client.DefaultRequestHeaders.Add("hiconline.protocol.content.transactionid", "asdfsdf");
client.DefaultRequestHeaders.Add("hiconline.protocol.remote.contenttype", "TestDataType");
client.DefaultRequestHeaders.Add("hiconline.protocol.remote.mode", "P");
client.DefaultRequestHeaders.Host = "peter:8001";
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plain"));
string opv = "Test Data";
HttpContent _content = new StringContent(opv);
_content.Headers.ContentType = new MediaTypeHeaderValue("application/xml");
_content.Headers.Add("contenttype", "TestDataType");
HttpResponseMessage response1 = await client.PostAsync(client.BaseAddress, _content);
if (response1.IsSuccessStatusCode)
{
Uri gizmoUrl = response1.Headers.Location;
result = response1.Content.ReadAsStringAsync().Result;
}
}
catch (Exception ex)
{
result = ex.Message;
}
return result;
}
}
Option 1 is better. RunAsync() already returns a task, so why create another one?
Even better would be return await RunAsync();. Even better would just be calling RunAsync directly, since the wrapper doesn't add anything.

Problems implementing ValidatingAntiForgeryToken attribute for Web API with MVC 4 RC

I'm making JSON-based AJAX requests and, with MVC controllers have been very grateful to Phil Haack for his Preventing CSRF with AJAX and, Johan Driessen's Updated Anti-XSRF for MVC 4 RC. But, as I transition API-centric controllers to Web API, I'm hitting issues where the functionality between the two approaches is markedly different and I'm unable to transition the CSRF code.
ScottS raised a similar question recently which was answered by Darin Dimitrov. Darin's solution involves implementing an authorization filter which calls AntiForgery.Validate. Unfortunately, this code does not work for me (see next paragraph) and - honestly - is too advanced for me.
As I understand it, Phil's solution overcomes the problem with MVC AntiForgery when making JSON requests in the absence of a form element; the form element is assumed/expected by the AntiForgery.Validate method. I believe that this may be why I'm having problems with Darin's solution too. I receive an HttpAntiForgeryException "The required anti-forgery form field '__RequestVerificationToken' is not present". I am certain that the token is being POSTed (albeit in the header per Phil Haack's solution). Here's a snapshot of the client's call:
$token = $('input[name=""__RequestVerificationToken""]').val();
$.ajax({
url:/api/states",
type: "POST",
dataType: "json",
contentType: "application/json: charset=utf-8",
headers: { __RequestVerificationToken: $token }
}).done(function (json) {
...
});
I tried a hack by mashing together Johan's solution with Darin's and was able to get things working but am introducing HttpContext.Current, unsure whether this is appropriate/secure and why I can't use the provided HttpActionContext.
Here's my inelegant mash-up.. the change is the 2 lines in the try block:
public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
try
{
var cookie = HttpContext.Current.Request.Cookies[AntiForgeryConfig.CookieName];
AntiForgery.Validate(cookie != null ? cookie.Value : null, HttpContext.Current.Request.Headers["__RequestVerificationToken"]);
}
catch
{
actionContext.Response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.Forbidden,
RequestMessage = actionContext.ControllerContext.Request
};
return FromResult(actionContext.Response);
}
return continuation();
}
My questions are:
Am I correct in thinking that Darin's solution assumes the existence of a form element?
What's an elegant way to mash-up Darin's Web API filter with Johan's MVC 4 RC code?
Thanks in advance!
You could try reading from the headers:
var headers = actionContext.Request.Headers;
var cookie = headers
.GetCookies()
.Select(c => c[AntiForgeryConfig.CookieName])
.FirstOrDefault();
var rvt = headers.GetValues("__RequestVerificationToken").FirstOrDefault();
AntiForgery.Validate(cookie != null ? cookie.Value : null, rvt);
Note: GetCookies is an extension method that exists in the class HttpRequestHeadersExtensions which is part of System.Net.Http.Formatting.dll. It will most likely exist in C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET MVC 4\Assemblies\System.Net.Http.Formatting.dll
Just wanted to add that this approach worked for me also (.ajax posting JSON to a Web API endpoint), although I simplified it a bit by inheriting from ActionFilterAttribute and overriding the OnActionExecuting method.
public class ValidateJsonAntiForgeryTokenAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
try
{
var cookieName = AntiForgeryConfig.CookieName;
var headers = actionContext.Request.Headers;
var cookie = headers
.GetCookies()
.Select(c => c[AntiForgeryConfig.CookieName])
.FirstOrDefault();
var rvt = headers.GetValues("__RequestVerificationToken").FirstOrDefault();
AntiForgery.Validate(cookie != null ? cookie.Value : null, rvt);
}
catch
{
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Forbidden, "Unauthorized request.");
}
}
}
Extension method using Darin's answer, with a check for the presence of the header. The check means that the resulting error message is more indicative of what's wrong ("The required anti-forgery form field "__RequestVerificationToken" is not present.") versus "The given header was not found."
public static bool IsHeaderAntiForgeryTokenValid(this HttpRequestMessage request)
{
try
{
HttpRequestHeaders headers = request.Headers;
CookieState cookie = headers
.GetCookies()
.Select(c => c[AntiForgeryConfig.CookieName])
.FirstOrDefault();
var rvt = string.Empty;
if (headers.Any(x => x.Key == AntiForgeryConfig.CookieName))
rvt = headers.GetValues(AntiForgeryConfig.CookieName).FirstOrDefault();
AntiForgery.Validate(cookie != null ? cookie.Value : null, rvt);
}
catch (Exception ex)
{
LogHelper.LogError(ex);
return false;
}
return true;
}
ApiController Usage:
public IHttpActionResult Get()
{
if (Request.IsHeaderAntiForgeryTokenValid())
return Ok();
else
return BadRequest();
}
An implementation using AuthorizeAttribute:
using System;
using System.Linq;
using System.Net.Http;
using System.Web;
using System.Web.Helpers;
using System.Web.Http;
using System.Web.Http.Controllers;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ApiValidateAntiForgeryToken : AuthorizeAttribute {
public const string HeaderName = "X-RequestVerificationToken";
private static string CookieName => AntiForgeryConfig.CookieName;
public static string GenerateAntiForgeryTokenForHeader(HttpContext httpContext) {
if (httpContext == null) {
throw new ArgumentNullException(nameof(httpContext));
}
// check that if the cookie is set to require ssl then we must be using it
if (AntiForgeryConfig.RequireSsl && !httpContext.Request.IsSecureConnection) {
throw new InvalidOperationException("Cannot generate an Anti Forgery Token for a non secure context");
}
// try to find the old cookie token
string oldCookieToken = null;
try {
var token = httpContext.Request.Cookies[CookieName];
if (!string.IsNullOrEmpty(token?.Value)) {
oldCookieToken = token.Value;
}
}
catch {
// do nothing
}
string cookieToken, formToken;
AntiForgery.GetTokens(oldCookieToken, out cookieToken, out formToken);
// set the cookie on the response if we got a new one
if (cookieToken != null) {
var cookie = new HttpCookie(CookieName, cookieToken) {
HttpOnly = true,
};
// note: don't set it directly since the default value is automatically populated from the <httpCookies> config element
if (AntiForgeryConfig.RequireSsl) {
cookie.Secure = AntiForgeryConfig.RequireSsl;
}
httpContext.Response.Cookies.Set(cookie);
}
return formToken;
}
protected override bool IsAuthorized(HttpActionContext actionContext) {
if (HttpContext.Current == null) {
// we need a context to be able to use AntiForgery
return false;
}
var headers = actionContext.Request.Headers;
var cookies = headers.GetCookies();
// check that if the cookie is set to require ssl then we must honor it
if (AntiForgeryConfig.RequireSsl && !HttpContext.Current.Request.IsSecureConnection) {
return false;
}
try {
string cookieToken = cookies.Select(c => c[CookieName]).FirstOrDefault()?.Value?.Trim(); // this throws if the cookie does not exist
string formToken = headers.GetValues(HeaderName).FirstOrDefault()?.Trim();
if (string.IsNullOrEmpty(cookieToken) || string.IsNullOrEmpty(formToken)) {
return false;
}
AntiForgery.Validate(cookieToken, formToken);
return base.IsAuthorized(actionContext);
}
catch {
return false;
}
}
}
Then just decorate your controller or methods with [ApiValidateAntiForgeryToken]
And add to the razor file this to generate your token for javascript:
<script>
var antiForgeryToken = '#ApiValidateAntiForgeryToken.GenerateAntiForgeryTokenForHeader(HttpContext.Current)';
// your code here that uses such token, basically setting it as a 'X-RequestVerificationToken' header for any AJAX calls
</script>
If it helps anyone, in .net core, the header's default value is actually just "RequestVerificationToken", without the "__". So if you change the header's key to that instead, it'll work.
You can also override the header name if you like:
services.AddAntiforgery(o => o.HeaderName = "__RequestVerificationToken")