How do I properly use PUT for updating something? - c#

I am trying to learn xamarin forms. I have this existing database that I wanted to be accessed by an App. Here I want to update something using web api/REST, I followed this guide for consuming REST. Unfortunately it's not working and I don't even know why.
How do I properly use PUT for updating something and what is wrong here?
WEB API class:
class GuestAcc
{
public string RefCode { get; set; }
public double Price { get; set; }
}
Xamarin Model:
public class GuestAcc
{
public string RefCode { get; set; }
public double Price { get; set; }
}
GuestAccountsController:
[ResponseType(typeof(void))]
public async Task<IHttpActionResult> UpdateBalance(GuestAcc guestAcc)
{
var guestAccounts = db.GuestAccounts.First(x => x.ReferenceCode == guestAcc.RefCode);
guestAccounts.Balance = guestAccounts.Balance - guestAcc.Price;
db.Entry(guestAccounts).State = EntityState.Modified;
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.NoContent);
}
Xamarin form:
private async void btn_proceed_clicked(object sender, EventArgs e)
{
GuestAcc guestAcc = new GuestAcc();
guestAcc.Price = 125;
guestAcc.RefCode = "user123";
var guestAccountURL = "http://192.168.8.100:56750/api/UpdateBalance/";
var uri_guestAccount = new Uri(string.Format(guestAccountURL, string.Empty));
var json = JsonConvert.SerializeObject(guestAcc);
var content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage response = null;
response = await client.PutAsync(uri_guestAccount, content);
if (response.IsSuccessStatusCode)
{
await DisplayAlert("Notice", "Success", "Ok");
}
}

You need to use the correct HTTP verb either in the action name or as an attribute to the method. like
[ResponseType(typeof(void))]
[HttpPut]
public async Task<IHttpActionResult> UpdateBalance(GuestAcc guestAcc)
{
var guestAccounts = db.GuestAccounts.First(x => x.ReferenceCode == guestAcc.RefCode);
guestAccounts.Balance = guestAccounts.Balance - guestAcc.Price;
db.Entry(guestAccounts).State = EntityState.Modified;
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.NoContent);
}
or in the name like
[ResponseType(typeof(void))]
public async Task<IHttpActionResult> PutBalanceAsync(GuestAcc guestAcc)
{
var guestAccounts = db.GuestAccounts.First(x => x.ReferenceCode == guestAcc.RefCode);
guestAccounts.Balance = guestAccounts.Balance - guestAcc.Price;
db.Entry(guestAccounts).State = EntityState.Modified;
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.NoContent);
}

Related

I am getting Error 415 while uploading image in Angular and .Net core project

I want to upload an image to my project, but unfortunately it gives an error of 415. Swagger does not give an error when I install it from above, but it gives an error when I install it from above Angular. What is the solution to this?
Backend Web Api Code;
[Produces("application/json", "text/plain")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(string))]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))]
[HttpPost("update")]
public async Task<IActionResult> Update([FromFrom] UpdatePresidentMessageCommand updatePresidentMessage)
{
var result = await Mediator.Send(updatePresidentMessage);
if (result.Success)
{
return Ok(result.Message);
}
return BadRequest(result.Message);
}
}
Backend Handlers Code;
if (request.Image != null)
{
var fileUpload = Core.Extensions.FileExtension.FileUpload(request.Image, fileType: Core.Enums.FileType.Image);
if (fileUpload != null)
{
isTherePresidentMessageRecord.Image = fileUpload.Path;
}
}
Angular Service Code;
updatePresidentMessage(presidentMessage: FormData): Observable<any> {
return this.httpClient.post('https://localhost:60021/Admin/api' + '/presidentMessages/update', presidentMessage,{ responseType: 'text' });
}
Angular Ts Code;
update(): void {
let presidentModel = Object.assign({}, this.presidentMessageFormGroup.value);
const formData = new FormData();
formData.append('id', presidentModel.id.toString());
formData.append('shortDescription', presidentModel.shortDescription);
formData.append('description', presidentModel.description);
for (const photo of this.photos) {
formData.append('image', photo, photo.name);
}
this.presidentMessageService.updatePresidentMessage(formData).subscribe(response => {
this.alertifyService.success(response);
});
}
onFileSelect(event: any) {
if (event.target.files.length > 0) {
for (const file of event.target.files) {
this.photos.push(file);
}
}
}
" const headers = new HttpHeaders().set('Content-Type', 'multipart/form-data');
" the error I get when I add;
enter image description here
UpdatePresidentMessageCommand Class;
public int ID { get; set; }
public string ShortDescription { get; set; }
public string Description { get; set; }
public IFormFile Image { get; set; }
public class UpdatePresidentMessageCommandHandler : IRequestHandler<UpdatePresidentMessageCommand, IResult>
{
private readonly IPresidentMessageRepository presidentMessageRepository;
private readonly IUserService userService;
public UpdatePresidentMessageCommandHandler(IUserService userService, IPresidentMessageRepository presidentMessageRepository)
{
this.userService = userService;
this.presidentMessageRepository = presidentMessageRepository;
}
[LogAspect(typeof(FileLogger))]
[SecuredOperation(Priority = 1)]
public async Task<IResult> Handle(UpdatePresidentMessageCommand request, CancellationToken cancellationToken)
{
var isTherePresidentMessageRecord = await this.presidentMessageRepository.GetAsync(u => u.ID == request.ID);
if (isTherePresidentMessageRecord == null)
{
return new ErrorResult(message: Messages.Error);
}
isTherePresidentMessageRecord.ShortDescription = request.ShortDescription;
isTherePresidentMessageRecord.Description = request.Description;
isTherePresidentMessageRecord.UpdateByUserID = this.userService.GetNameIdentifier();
isTherePresidentMessageRecord.UpdateDateTime = System.DateTime.Now;
if (request.Image != null)
{
var fileUpload = Core.Extensions.FileExtension.FileUpload(request.Image, fileType: Core.Enums.FileType.Image);
if (fileUpload != null)
{
isTherePresidentMessageRecord.Image = fileUpload.Path;
}
}
this.presidentMessageRepository.Update(isTherePresidentMessageRecord);
await this.presidentMessageRepository.SaveChangesAsync();
return new SuccessResult(Messages.Updated);
}
}
Try to set the correct 'Content-Type' on the header of your POST request.

Xamarin.Forms Consume API

Can someone help me?
I'm try to consume api in my apps (I'm still learning). I can successfully call the api and get the data, but debug ended in DeserializeObject.
Can someone help me, and tell what must I do? or reference how to fix this?
This is my code:
My ViewModels
from ViewModels I call GetHeroes(), which calls my class in services.
public class DotaViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public DotaViewModel()
{
GetHeroes(); // Start From Here
}
IDotaApi _rest = DependencyService.Get<IDotaApi>();
private ObservableCollection<Hero> heroes;
public ObservableCollection<Hero> Heroes
{
get { return heroes; }
set
{
heroes = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Heroes"));
}
}
public async void GetHeroes()
{
var result = await _rest.getheroes(); // Go Inside Here
if (result != null)
{
}
}
}
My Services
I got the data and stored it to var result, but my debug just when I DeserializeObject.
public class DotaApi : IDotaApi
{
string Base_url = "https://api.opendota.com/api/heroes";
public async Task<ObservableCollection<Hero>> getheroes()
{
string url = Base_url;
HttpClient client = new HttpClient();
HttpResponseMessage responseMessage = await client.GetAsync(url);
if (responseMessage.StatusCode == System.Net.HttpStatusCode.OK)
{
var result = await responseMessage.Content.ReadAsStringAsync(); // I Got Data Here
var json = JsonConvert.DeserializeObject<ObservableCollection<Hero>>(result); // But Stuck Here
return json;
}
return null;
}
}
This is My Model
public class Hero
{
public int id { get; set; }
public string name { get; set; }
public string localized_name { get; set; }
public string primary_attr { get; set; }
public string attack_type { get; set; }
public List<string> roles { get; set; }
public int legs { get; set; }
//[
//{"id":1,
//"name":"npc_dota_hero_antimage",
//"localized_name":"Anti-Mage",
//"primary_attr":"agi",
//"attack_type":"Melee",
//"roles":["Carry","Escape","Nuker"],
//"legs":2}
//}
//]
}
Try to implement the API calling method like below and add a debug point to the exception and check the issue
public async Task<ObservableCollection<Hero>> getheroes()
{
string Base_url = "https://api.opendota.com/api/heroes";
using (var httpClient = new HttpClient())
{
var data = new ObservableCollection<Hero>();
try
{
var content = new StringContent(requestBody, Encoding.UTF8, "application/json");
var result = await httpClient.GetAsync(Base_url);
var response = await result.Content.ReadAsStringAsync();
data = JsonConvert.DeserializeObject<ObservableCollection<Hero>>(response);
if (result.IsSuccessStatusCode && result.StatusCode == HttpStatusCode.OK)
{
return data;
}
return null;
}
catch (Exception exp)
{
return null;
}
}
}
Thanks For all,
I finally solved the problem,
this is purely my fault cause I didn't really understand how it worked before,
so i try to understand more deeply about ObservableCollection and how to debug
now i know, there is nothing wrong with DeserializeObject like i said in my question (sorry for that), the problem is in GetHeroes() function
public async void GetHeroes()
{
var result = await _rest.getheroes();
if (result != null)
{
Heroes = result; // i missed this code, so. I haven't entered my data before
}
}

WebAPI HttpContent convert to typed object

I am working on custom filter which should accomplish simple thing. All my APIs wrapped into 'Response' object. I want to fill in all properties using filter. This is code I have for the filter:
public class MeteringFilter : IActionFilter
{
public Task<HttpResponseMessage> ExecuteActionFilterAsync(
HttpActionContext actionContext,
CancellationToken cancellationToken,
Func<Task<HttpResponseMessage>> continuation)
{
var attribute =
actionContext.ActionDescriptor.GetCustomAttributes<MeterAttribute>(true).SingleOrDefault() ??
actionContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<MeterAttribute>(true).SingleOrDefault();
if (attribute == null) return continuation();
var operation = actionContext.ActionDescriptor.ActionName;
var user = actionContext.RequestContext.Principal.Identity.Name;
var started = DateTimeOffset.Now;
return continuation().ContinueWith(t =>
{
var completed = DateTimeOffset.Now;
var duration = completed - started;
var c = t.Result.Content;
// This is code which does not work but I like to have:
// When debugger stops here I can see Content.Value and my object but I can't use this property like below
var cv = t.Result.Content.Value as Response<object>;
return t.Result;
});
}
public bool AllowMultiple => true;
}
I found similar question where it was suggested to do var c = t.Result.Content.ReadAsAsync(typeof(Response<>)); but I can't do this because I can't make lambda function async in this case.
Any suggestion on how to get typed object out of HttpContent so I can assign properties before it returns to caller?
Here is Response<T>
public class Response<T>
{
public string Url { get; set; }
public DateTime ServerTime { get; set; }
public TimeSpan TimeTook { get; set; }
public T Data { get; set; }
public Error Error { get; set; }
}
EDIT
Here is how code looks now. I do get access to object, but webservice does not respond with data I fill to client. It seems that code executed later after serialization/media formatting takes place.
I guess question becomes how do I add generic "handler" before web service returns but with access to beginning of call (so I can measure times, see request params, etc)
return continuation().ContinueWith(t =>
{
var c = t.Result.Content.ReadAsAsync(typeof(Response<object>), cancellationToken);
if (c.Result is Response<object> response)
{
Debug.WriteLine("Adding times");
response.ServerTime = startedOn;
response.TimeTook = DateTime.Now - startedOn;
}
return t.Result;
}, cancellationToken);
EDIT 2:
Here is sample web api method which I want to intercept:
[HttpGet]
public Response<LookupResponseData> Carrier(int? key = null, string id = "")
{
return this.GetKeyIdBundleForLookup("Carriers", key, id);
}
private Response<LookupResponseData> GetKeyIdBundleForLookup(string lookupId, int? key, string id)
{
if (!key.HasValue && string.IsNullOrEmpty(id))
return new Response<LookupResponseData>
{
Error = new Error { Code = ErrorCodes.InvalidQueryParameter, Message = "Either key or id must be specified" }
};
var r = new Response<LookupResponseData>();
try
{
this.LookupService.GetKeyIdDescription(this.AccountId, lookupId, key, id, out var keyResult, out var idResult, out var description);
if (!keyResult.HasValue)
return new Response<LookupResponseData>
{
Error = new Error { Code = ErrorCodes.InvalidOrMissingRecord, Message = "No record found for parameters specified" }
};
r.Data = new LookupResponseData { Key = keyResult.Value, Id = idResult, Description = description };
}
catch (Exception ex)
{
this.LoggerService.Log(this.AccountId, ex);
return new Response<LookupResponseData>
{
Error = new Error { Code = ErrorCodes.Unknown, Message = "API Call failed, please contact support. Details logged." }
};
}
return r;
}
All my APIs wrapped into 'Response' object.
First you can simplify your results by creating a implicit operators:
public class Response
{
public string Url { get; set; }
public DateTime ServerTime { get; set; }
public TimeSpan TimeTook { get; set; }
}
public class Response<T> : Response
{
public T Data { get; set; }
public Error Error { get; set; }
public static implicit operator Response<TData>(TData data)
{
var result = new Response<TData>
{
Data = data,
};
return result;
}
public static implicit operator Response<TData>(Error error)
{
var result = new Response<TData>
{
Error = error,
};
return result;
}
}
Now it should be easier to really ignore the repeated code of creating the response:
private Response<LookupResponseData> GetKeyIdBundleForLookup(
string lookupId, int? key, string id)
{
if (!key.HasValue && string.IsNullOrEmpty(id))
return new Error
{
Code = ErrorCodes.InvalidQueryParameter,
Message = "Either key or id must be specified"
};
try
{
this.LookupService.GetKeyIdDescription(this.AccountId,
lookupId,
key,
id,
out var keyResult,
out var idResult,
out var description);
if (!keyResult.HasValue)
return new Error
{
Code = ErrorCodes.InvalidOrMissingRecord,
Message = "No record found for parameters specified"
};
return new LookupResponseData
{
Key = keyResult.Value,
Id = idResult, Description = description
};
catch (Exception ex)
{
this.LoggerService.Log(this.AccountId, ex);
return new Error
{
Code = ErrorCodes.Unknown,
Message = "API Call failed, please contact support. Details logged." }
};
}
}
Then you can create an Core Async Action Filter:
public class SampleAsyncActionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
// do something before the action executes
var started = DateTimeOffset.Now;
// Action Executes
var resultContext = await next();
// do something after the action executes; resultContext.Result will be set
if (result.Context.Result is Response response)
{
response.ServerTime = started;
response.TimeTook = DateTimeOffset.Now - started;
}
}
}
Or Non-Core (MVC):
public class SampleActionFilter : ActionFilterAttribute
{
private const string TimerKey = nameof(SampleActionFilter ) + "_TimerKey";
public override void OnActionExecuting(ActionExecutingContext context)
{
context.HttpContext.Items[TimerKey] = DateTimeOffset.Now;
}
public override void OnActionExecuted(ActionExecutedContext context)
{
if (context.Result is Response response)
&& context.HttpContext.Items[TimerKey] is DateTimeOffset started)
{
response.ServerTime = started;
response.TimeTook = DateTimeOffset.Now - started;
}
}
Or Non-Core (WebApi):
public class SampleActionFilter : ActionFilterAttribute
{
private const string TimerKey = nameof(SampleActionFilter ) + "_TimerKey";
public override void OnActionExecuting(HttpActionContext context)
{
context.Request.Properties[TimerKey] = DateTimeOffset.Now;
}
public override void OnActionExecuted(HttpActionExecutedContext context)
{
if (context.Result is Response response)
&& context.Request.Properties[TimerKey] is DateTimeOffset started)
{
response.ServerTime = started;
response.TimeTook = DateTimeOffset.Now - started;
}
}
I tweaked your code. I hope it helps.
I couldn't check syntax errors though
return await continuation().ContinueWith(async t =>
{
var result = await t;
var c = await result.Content.ReadAsAsync(typeof(Response<object>), cancellationToken);
if (c is Response<object> response)
{
Debug.WriteLine("Adding times");
response.ServerTime = startedOn;
response.TimeTook = DateTime.Now - startedOn;
}
return result;
}, cancellationToken);

Client.PostAsync (with Json) Gets 400 Bad Request

I am trying to create an Integration Test for my .Net Core Web Api
But I am always getting a 400 Bad Request response. I am sharing details below
Here is my Controller method
public IActionResult UpdateProductById([FromBody]int id, string description)
{
var result = ProductService.UpdateProductById(id, description);
if (result.Exception == null)
return Ok(result);
else
return BadRequest(result.Exception.Message);
}
Here is my test class (which tries to post)
[Fact]
public async Task UpdateProductById_Test_WithProduct()
{
var product = new
{
id = 1,
description = "foo"
};
var productObj= JsonConvert.SerializeObject(product);
var buffer = System.Text.Encoding.UTF8.GetBytes(productObj);
var byteContent = new ByteArrayContent(buffer);
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var result = await _tester.Client.PostAsync("/api/1.0/UpdateProductById", byteContent);
result.StatusCode.Should().Be(HttpStatusCode.OK);
}
The test is sending all the content in the body of the request, yet the action is only binding the id. Most likely the description is null and causing an issue with the update.
Create a model to hold the data
public class ProductModel {
public int id { get; set; }
public string description { get; set; }
}
Refactor action to get the content from the body of the request
[HttpPost]
public IActionResult UpdateProductById([FromBody]ProductModel model) {
if(ModelState.IsValid) {
var result = ProductService.UpdateProductById(model.id, model.description);
if (result.Exception == null)
return Ok(result);
else
return BadRequest(result.Exception.Message);
}
return BadRequest(ModelState);
}

IFormFile always returns Null value on dotnet core 2.0

I have a post request for my PhotosController class. When I test this code, it always returns a null value. I don't see file details.
Basically it gets the userid and PhotoDto and it should return photo. I use Cloudinary service to store photos. My API settings of the clodinary is located inside appsettings.json file and there is no problem for those settings. When I debug the code, the problem occurs where if (file.Length > 0) is. I am guessing that there is no file.
Here is my PhotoForCreationDto file:
public class PhotoForCreationDto
{
public string Url { get; set; }
public IFormFile File { get; set; }
public string Description { get; set; }
public DateTime DateAdded { get; set; }
public string PublicId { get; set; }
public PhotoForCreationDto()
{
DateAdded = DateTime.Now;
}
}
And here is my PhotosController file:
[Authorize]
[Route("api/users/{userId}/photos")]
public class PhotosController : Controller
{
private readonly IDatingRepository _repo;
private readonly IMapper _mapper;
private readonly IOptions<CloudinarySettings> _cloudinaryConfig;
private Cloudinary _cloudinary;
public PhotosController(IDatingRepository repo,
IMapper mapper,
IOptions<CloudinarySettings> cloudinaryConfig)
{
_mapper = mapper;
_repo = repo;
_cloudinaryConfig = cloudinaryConfig;
Account acc = new Account(
_cloudinaryConfig.Value.CloudName,
_cloudinaryConfig.Value.ApiKey,
_cloudinaryConfig.Value.ApiSecret
);
_cloudinary = new Cloudinary(acc);
}
[HttpGet("{id}", Name = "GetPhoto")]
public async Task<IActionResult> GetPhoto(int id)
{
var photoFromRepo = await _repo.GetPhoto(id);
var photo = _mapper.Map<PhotoForReturnDto>(photoFromRepo);
return Ok(photo);
}
[HttpPost]
public async Task<IActionResult> AddPhotoForUser(int userId, PhotoForCreationDto photoDto)
{
var user = await _repo.GetUser(userId);
if (user == null)
return BadRequest("Could not find user");
var currentUserId = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value);
if (currentUserId != user.Id)
return Unauthorized();
var file = photoDto.File;
var uploadResult = new ImageUploadResult();
if (file.Length > 0)
{
using (var stream = file.OpenReadStream())
{
var uploadParams = new ImageUploadParams()
{
File = new FileDescription(file.Name, stream)
};
uploadResult = _cloudinary.Upload(uploadParams);
}
}
photoDto.Url = uploadResult.Uri.ToString();
photoDto.PublicId = uploadResult.PublicId;
var photo = _mapper.Map<Photo>(photoDto);
photo.User = user;
if (!user.Photos.Any(m => m.IsMain))
photo.IsMain = true;
user.Photos.Add(photo);
if (await _repo.SaveAll())
{
var photoToReturn = _mapper.Map<PhotoForReturnDto>(photo);
return CreatedAtRoute("GetPhoto", new { id = photo.Id }, photoToReturn);
}
return BadRequest("Could not add the photo");
}
}
Here is error at postman:
I tried to use [FromBody] but it also didn't work.
I would appriciate any help.
When submitting file from Postman, make sure you don't fill Content-Type header by yourself. Postman will set it automatically to multipart/form-data value.
Setting Content-Type header to application/json prevents ASP.Net Core from correct processing of request data. That's why IFormFile property is not filled and is set to null.

Categories