Calling Delete rest API issue - c#

I'm trying to delete an item from a calendar but I feel the call to the API is incorrect.
I'm getting a 404 status code & this error:
System.Net.Http.Json.HttpClientJsonExtensions.DeleteFromJsonAsyncCore[T](Task`1 taskResponse, JsonSerializerOptions options, CancellationToken cancellationToken
Below is my client code:
async Task DeleteAppointment(SchedulerDeleteEventArgs e)
{
UvwHolidayPlanner holidayPlannerItem = e.Item as UvwHolidayPlanner;
holidayPlanner.Pk = holidayPlannerItem.Pk;
await http.CreateClient("ClientSettings").DeleteFromJsonAsync<UvwHolidayPlanner>($"{_URL}/api/HolidayPlannerOperations/DeleteHolidayPlanner/{holidayPlanner.Pk}");
HolidayPlanners = (await http.CreateClient("ClientSettings").GetFromJsonAsync<List<UvwHolidayPlanner>>($"{_URL}/api/lookup/HolidayPlanner"))
.OrderBy(t => t.Title)
.ToList();
StateHasChanged();
}
Below is my API controller:
[Route("api/[controller]")]
[ApiController]
public class HolidayPlannerOperationsController : ControllerBase
{
[HttpDelete]
[Route("DeleteHolidayPlanner/{PK}")]
public void Delete(int PK)
{
string SQLSTE = "EXEC [dbo].[usp_DeleteHolidayPlanner] #PK";
using (var context = new TestAppContext())
{
List<SqlParameter> param = new List<SqlParameter>
{
new SqlParameter { ParameterName = "#PK", Value = PK },
};
context.Database.ExecuteSqlRaw(SQLSTE, param);
}
}
}
After doing as advised at the bottom and changing the route from [Route("DeleteHolidayPlanner")] to [Route("DeleteHolidayPlanner/{PK}")]
I'm now getting the below error message:
Error: System.Text.Json.JsonException: The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true. Path: $ | LineNumber: 0 | BytePositionInLine: 0.
---> System.Text.Json.JsonReaderException: The input does not contain
any JSON tokens. Expected the input to start with a valid JSON token,
when isFinalBlock is true. LineNumber: 0 | BytePositionInLine: 0.

Related

Calling cosmos DB stored procedure in Azure Function

I have a stored procedure in Azure Cosmos DB that will delete record from my Cosmos DB. I tried to call that stored procedure from my Azure function, but I am getting an error related to json parsing when executing the stored procedure (client.ExecuteStoredProcedureAsync). I already tried changing the partition key to "id" but I still get the same error.
Here is the error message that I got:
"Failed to deserialize stored procedure response or convert it to type 'System.String': Unexpected character encountered while parsing value: {. Path '', line 1, position 1."
Stack trace
at Microsoft.Azure.Documents.Client.StoredProcedureResponse1..ctor(DocumentServiceResponse response, JsonSerializerSettings serializerSettings)\r\n at Microsoft.Azure.Documents.Client.DocumentClient.ExecuteStoredProcedurePrivateAsync[TValue](String storedProcedureLink, RequestOptions options, IDocumentClientRetryPolicy retryPolicyInstance, CancellationToken cancellationToken, Object[] procedureParams)\r\n at Microsoft.Azure.Documents.BackoffRetryUtility1.ExecuteRetryAsync(Func1 callbackMethod, Func3 callShouldRetry, Func1 inBackoffAlternateCallbackMethod, TimeSpan minBackoffForInBackoffCallback, CancellationToken cancellationToken, Action1 preRetryCallback)\r\n at Microsoft.Azure.Documents.ShouldRetryResult.ThrowIfDoneTrying(ExceptionDispatchInfo capturedException)\r\n at Microsoft.Azure.Documents.BackoffRetryUtility1.ExecuteRetryAsync(Func1 callbackMethod, Func3 callShouldRetry, Func1 inBackoffAlternateCallbackMethod, TimeSpan minBackoffForInBackoffCallback, CancellationToken cancellatio
nToken, Action1 preRetryCallback)\r\n at FunctionApp2.Function1.Run(HttpRequest req, IAsyncCollector1 documentsOut, ILogger log) in C:\Users\source\repos\FunctionApp2\FunctionApp2\Function1.cs:line 36"
Stored procedure deleteStoreSample:
function bulkDeleteProcedure() {
var collection = getContext().getCollection();
var collectionLink = collection.getSelfLink();
var response = getContext().getResponse();
var responseBody = {
deleted: 0,
continuation: true
};
query = 'SELECT * FROM c WHERE c.submittedDate > "2002-08-31"';
// Validate input.
if (!query)
throw new Error("The query is undefined or null.");
tryQueryAndDelete();
// Recursively runs the query w/ support for continuation tokens.
// Calls tryDelete(documents) as soon as the query returns documents.
function tryQueryAndDelete(continuation) {
var requestOptions = {continuation: continuation};
var isAccepted = collection.queryDocuments(collectionLink, query, requestOptions, function (err, retrievedDocs, responseOptions) {
if (err)
throw err;
if (retrievedDocs.length > 0) {
// Begin deleting documents as soon as documents are returned form the query results.
// tryDelete() resumes querying after deleting; no need to page through continuation tokens.
// - this is to prioritize writes over reads given timeout constraints.
tryDelete(retrievedDocs);
} else if (responseOptions.continuation) {
// Else if the query came back empty, but with a continuation token; repeat the query w/ the token.
tryQueryAndDelete(responseOptions.continuation);
} else {
// Else if there are no more documents and no continuation token - we are finished deleting documents.
responseBody.continuation = false;
response.setBody(responseBody);
}
});
// If we hit execution bounds - return continuation: true.
if (!isAccepted) {
response.setBody(responseBody);
}
}
// Recursively deletes documents passed in as an array argument.
// Attempts to query for more on empty array.
function tryDelete(documents) {
if (documents.length > 0) {
// Delete the first document in the array.
var isAccepted = collection.deleteDocument(documents[0]._self, {}, function (err, responseOptions) {
if (err) throw err;
responseBody.deleted++;
documents.shift();
// Delete the next document in the array.
tryDelete(documents);
});
// If we hit execution bounds - return continuation: true.
if (!isAccepted) {
response.setBody(responseBody);
}
} else {
// If the document array is empty, query for more documents.
tryQueryAndDelete();
}
}
Azure function:
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
[CosmosDB(
databaseName: "dbName",
collectionName: "collectionName",
ConnectionStringSetting = "CosmosDbConnectionString")]IAsyncCollector<dynamic> documentsOut,
ILogger log)
{
try
{
log.LogInformation("C# HTTP trigger function processed a request.");
string endpoint = "https://account.documents.azure.com:443/";
string key = "gixxxx==";
var client = new DocumentClient(new Uri(endpoint), key);
Uri uri = UriFactory.CreateStoredProcedureUri("service-order", "ServiceOrder", "deleteStoreSample");
RequestOptions options = new RequestOptions { PartitionKey = new PartitionKey("/id") };
var result = await client.ExecuteStoredProcedureAsync<string>(uri, options);
var response = result.Response;
return new OkObjectResult(response);
}
catch (Exception ex)
{
throw ex;
}
}
I believe the issue is with the following line of code:
RequestOptions options = new RequestOptions { PartitionKey = new PartitionKey("/id") };
You actually need to specify the value of the partition key here and not the attribute name.
Please try changing it to something like:
RequestOptions options = new RequestOptions { PartitionKey = new PartitionKey("partition-key-value") };// Replace "partition-key-value" with actual value

Unsupported Media Type 415, but in Postman works fine

After registration user get a link, which consist of userId and token. When user click on it - angular project opens, then angular takes userId and token from link, and send a post method to backend for verifying email.
Link example
http://localhost:4200/user/confirmemail/?userId=9bc9a474-d10c-438b-8953-1c32fc57551b&token=Q2ZESjhPRkF6d3BPQ1E5TmtLbWplTnlIZ3g3bzJEVEZQZDQ3MFNSblExaWxBTWh3MmdSMWl2ZkU3djZxcWR3bmY4OFMwd2l6STZOY3VMR2FoZ1ZCM28rWFo1YlJhejhOTE5pamFFdGRETTNiNGttT0lOQ2dZVmdLRVlRdWlKRmtQMVpEWHE0b2t2NVBQZTZ0bkR3cUdrb3JiMWRIQUpRUE5pMTZjTW96YUdJcVZBUUxPSG5pd3NDU3BDeTBNREMvMTVyTlhUNUpCL2tRZTJWMjJlTzVHZ1dDbEh5VWNESGNsNlVTQkpXZ1FJaThDTk1kcUovcmdtV0ZEdEQza2hXR1p6V0pEdz09
Post method, which verify email:
[HttpPost("confirmEmail/{userId}")]
public async Task<IActionResult> ConfirmEmail(string userId, [FromBody]string token)
{
var codeDecodedBytes = WebEncoders.Base64UrlDecode(token);
var codeDecoded = Encoding.UTF8.GetString(codeDecodedBytes);
var user = await _userManager.FindByIdAsync(userId);
var result = await _userManager.ConfirmEmailAsync(user, codeDecoded);
return Ok(result);
}
It works fine in postman:
Error in Angular project:
Getting userId and token in Angular
userId: string;
token: string;
constructor(private activatedRoute: ActivatedRoute, private authService: AuthService) {
this.activatedRoute.queryParams.subscribe(params => {
this.userId = params['userId'];
this.token = params['token'];
});
}
ngOnInit(): void {
this.confirmEmail();
}
confirmEmail(){
this.authService.confirmEmail(this.userId, this.token).subscribe(data => { console.log("success")});
}
(AuthService) Sending data to backend
confirmEmail(userId: string, token: string){
console.log(userId);
console.log(token);
return this.http.post(this.authUrl + `confirmemail/${userId}`, token);
}
Try to set Content-Type to json type:
confirmEmail(userId: string, token: string) {
const body = JSON.stringify(token);
const options = {
headers: new HttpHeaders().append('Content-Type', 'application/json')
};
return this.http.post(this.authUrl + `confirmemail/${userId}`, body, options)
.pipe(
map(res => res)
, catchError(this.handleError())
);
}

Get Http response headers and content Angular 6

I have the following web server method, that returns data to our front-end applicaiton.
[FunctionName("SearchCustomerBySearchTerm")]
public static async Task<HttpResponseMessage> SearchCustomerBySearchTerm([HttpTrigger(AuthorizationLevel.Function, WebRequestMethods.Http.Get, Route = "Customer/SearchCustomerBySearchTerm/{searchTerm}/pageSize/{pageSize}")]HttpRequestMessage req, TraceWriter log, string searchTerm, int pageSize)
{
try
{
var continuationToken = req.Headers.TryGetValues("continuationToken", out IEnumerable<string> values) ? values.FirstOrDefault() : null;
PagedResponse<CustomerSearchResult> pagedResponse = await _customerComponent.FindCustomerBy(searchTerm, continuationToken, pageSize);
if (pagedResponse == null) return req.CreateResponse(HttpStatusCode.NoContent, $"Could not find any data related to {searchTerm}");
HttpResponseMessage responseMessage = req.CreateResponse(HttpStatusCode.OK, pagedResponse.Results);
responseMessage.Content.Headers.Add("continuationToken", pagedResponse.Continuation);
responseMessage.Content.Headers.Add("Access-Control-Expose-Headers", "*");
return responseMessage;
}
catch (Exception ex)
{
log.Error(ex.Message);
return req.CreateResponse(HttpStatusCode.InternalServerError, "Something went wrong. Could not search for customers");
}
}
I am allowing all headers to be exposed, by adding the Access-Control-Expose-Headers.
From my Angular application, I am doing the request as follow:
searchCustomersPaged(searchTerm: string, continuationToken: string): Observable<HttpResponse<CustomerSearchResult>> {
let customHeaders = new HttpHeaders().set("continuationToken", this.currentContinuationToken);
const url = "http://localhost:7071/api/Customer/SearchCustomerBySearchTerm/andrew/pageSize/10";
const parsedUrl = encodeURI(url);
return this.http.get<HttpResponse<CustomerSearchResult>>(parsedUrl, { headers: customHeaders });
}
As you can see above, I am expecting an HttpResponse<CustomerSearch> back.
Here is how I try and read my headers:
nextClikcedHandle(continuationToken: string): void {
this.customerService.searchCustomersPaged(this.customerService.searchTerm, this.currentContinuationToken)
.subscribe(resp => {
//add current continuation token, to previous now, as this will be used for 'previous' searching
this.previousContinuationTokens.push(this.currentContinuationToken);
//set next continuation token received by server
this.currentContinuationToken = resp.headers.get('continuationToken');
//return search results
this.customerService.searchResults.next(resp.body);
});
}
With the above code, the resp.headers and the resp.body is always undefined. Why is this happening?
If I look at the Network tab within Chrome, I can see my data is returned, as well as my header.
What am I doing wrong?
I found a useful article here:
By default the HttpClient returns the body of the response. You can
pass-in an object with an observe key set to a value of ‘response’ to
get the full response. This can be useful to inspect for certain
headers:
So I changed my code as follow, with the added observe key.
searchCustomersPaged(searchTerm: string, continuationToken: string): Observable<HttpResponse<CustomerSearchResult>> {
let customHeaders = new HttpHeaders().set("continuationToken", this.currentContinuationToken);
const url = "http://localhost:7071/api/Customer/SearchCustomerBySearchTerm/andrew/pageSize/10";
const parsedUrl = encodeURI(url);
return this.http.get<CustomerSearchResult>(parsedUrl, { headers: customHeaders, observe: 'response' });
}
After changing above method, I could query body and headers as per normal:
nextClikcedHandle(continuationToken: string): void {
this.customerService.searchCustomersPaged(this.customerService.searchTerm, this.currentContinuationToken)
.subscribe(resp => {
//add current continuation token, to previous now, as this will be used for 'previous' searching
this.previousContinuationTokens.push(this.currentContinuationToken);
//set next continuation token received by server
this.currentContinuationToken = resp.headers.get('continuationToken');
//return search results
this.customerService.searchResults.next(resp.body);
});

Call to ApplicationTokenProvider.LoginSilentAsync never returns

From my local PC I'm trying to connect to the AzureBillingAPI using the following code :
var serviceCreds = await ApplicationTokenProvider.LoginSilentAsync(tenantDomain, clientId, secret);
This methods never returns. Debug output displays the following :
Microsoft.IdentityModel.Clients.ActiveDirectory Verbose: 1 : 07/03/2017 14:02:07: e608ec23-675f-4828-aa65-72479409ec63 - TokenCache: Looking up cache for a token...
iisexpress.exe Information: 0 : 07/03/2017 14:02:07: e608ec23-675f-4828-aa65-72479409ec63 - TokenCache: Looking up cache for a token...
Microsoft.IdentityModel.Clients.ActiveDirectory Information: 2 : 07/03/2017 14:02:07: e608ec23-675f-4828-aa65-72479409ec63 - TokenCache: No matching token was found in the cache
iisexpress.exe Information: 0 : 07/03/2017 14:02:07: e608ec23-675f-4828-aa65-72479409ec63 - TokenCache: No matching token was found in the cache
Question: how do I connect ?
Thanks.
According to your description, I guess the reason why this methods never returns is you called the async method in the sync Controller method with wait keywords.
If you call the getResultAsync as below, it will never return the result :
public ActionResult Index()
{
getResultAsync().Wait();
return View();
}
public static async Task getResultAsync()
{
string subscription = "subid";
string tenantid = "tenantid ";
string clientId = "clientId ";
string key = "key";
var serviceCreds = await ApplicationTokenProvider.LoginSilentAsync(tenantDomain, clientId, key);
BillingManagementClient b1 = new BillingManagementClient(serviceCreds) { SubscriptionId = subscription };
var result = b1.Operations.List();
}
I suggest you could change the index method as below, it will work well:
public async Task<ActionResult> Index()
{
await getResultAsync();
return View();
}
Result:

How to catch all exceptions in Web API 2?

I'm writing a RESTful API in Web API and I'm not sure how to handle errors effectively. I want the API to return JSON, and it needs to consist of the exact same format every single time - even on errors. Here are a couple of examples of what a successful and a failed response might look like.
Success:
{
Status: 0,
Message: "Success",
Data: {...}
}
Error:
{
Status: 1,
Message: "An error occurred!",
Data: null
}
If there is an exception - any exception at all, I want to return a response that is formed like the second one. What is the foolproof way to do this, so that no exceptions are left unhandled?
Implement IExceptionHandler.
Something like:
public class APIErrorHandler : IExceptionHandler
{
public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
var customObject = new CustomObject
{
Message = new { Message = context.Exception.Message },
Status = ... // whatever,
Data = ... // whatever
};
//Necessary to return Json
var jsonType = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
var response = context.Request.CreateResponse(HttpStatusCode.InternalServerError, customObject, jsonType);
context.Result = new ResponseMessageResult(response);
return Task.FromResult(0);
}
}
and in the configuration section of WebAPI (public static void Register(HttpConfiguration config)) write:
config.Services.Replace(typeof(IExceptionHandler), new APIErrorHandler());

Categories

Resources