Web API Post not returning response - asp.net

I have a Web API that gets call by this method:
public async Task<Model> AddModel(string token, Model newModel)
{
HttpContent content = await HttpHelper.Request(token, baseUrl, url, newModel, HttpRequestType.POST);
return await content.ReadAsAsync<Model>();
}
The Web API is successfully called and I can add a Model. This is the Web API method that gets called:
[Route("webapi/models/addModel")]
[HttpPost]
public async Task<ModelDto> AddWithoutResetingDefault(ModelDto newModel)
{
ModelService modelService = new ModelService();
return modelService.AddModel(newModel);
}
The problem is after the successful add, it doesn't return to the calling code anymore (I have a breakpoint that doesn't get hit). There are no console errors in the browser, I enclosed in a try-catch the calling code and the called code but there were no exceptions thrown.
Also, after the first click to add, if I try to refresh the browser, it takes a really long time to reload the browser (I don't know if being async has something to do with it).

(I don't know if being async has something to do with it) - Yes it has
your Api method
public async Task<ModelDto> AddWithoutResetingDefault(ModelDto newModel)
{
ModelService modelService = new ModelService();
return modelService.AddModel(newModel);
}
is marked as Async method, but the code inside is Sync. And that is the problem, if your modelService.AddModel(newModel); is async, then do
return await modelService.AddModel(newModel);
if its not then there is no point in making the AddWithoutResetingDefault
method async, hence remove Aysnc and simply do a sync method like
public ModelDto AddWithoutResetingDefault(ModelDto newModel)
{
ModelService modelService = new ModelService();
return modelService.AddModel(newModel);
}

Related

Inform the user of unauthorized method call

I am currently developing a web application using SignalR Core. I am using an AuthorizeAttribute (without roles) to ensure only authenticated users will connect to our Hub.
For certain methods a specific policy is required. These methods are also decorated with an AuthorizeAttribute containing a specific policy. Here is my Hub code:
[Authorize]
public class CustomerHub : Hub
{
public async Task SimpleMethod()
{
// Do things that don't require additional authorization
await Task.CompletedTask;
}
[Authorize(Policies.IsAdmin)]
public async Task AdvancedMethod()
{
// Do things that require admin authorization
await Task.CompletedTask;
}
public async Task ErrorMethod()
{
await Task.CompletedTask;
throw new NotImplementedException();
}
}
It all works as intended. I am able to connect to the Hub and call the SimpleMethod and the debugger does not step into the AdvancedMethod when I call it with insufficient rights. However when a user is not authorized to call a specific method I would like to inform them about this. But no specific error or message is sent back to the client in this case. I did implement a custom IHubFilter which informs the user of errors, but it turns out the AuthorizeAttribute is evaluated before this HubFilter is invoked.
public class CustomHubExceptionsFilter : IHubFilter
{
public async ValueTask<object?> InvokeMethodAsync(HubInvocationContext invocationContext, Func<HubInvocationContext, ValueTask<object?>> next)
{
try
{
var result = await next(invocationContext);
return result;
}
catch (Exception ex)
{
await invocationContext.Hub.Clients.Caller.SendCoreAsync("Error", new object?[] { $"Oops, something went wrong. Technical details: {ex.GetType().Name}" });
throw;
}
}
}
I also tried adding a custom AuthorizationHandler which returns an error when a user is unauthorized. However this causes the connection to be closed which is not a great solution in my opinion.
Is there a away to inform the user using AuthorizeAttributes? I could create a custom IHubFilter which checks whether the user is authorized. But that would require a lot of custom code. Is there a simpler/more native method available?
I suggest you could consider calling the Hub method's ErrorMethod directly inside the custom AuthorizationHandler inside of directly return the exception.
For example:
We need store the connection id inside the request, then inside the AuthorizationHandler we could get it and then use hub method to return the error to the client.
Like this :
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ConnectionRequirement requirement)
{
var connectionIdClaim = context.User.FindFirst("connectionId");
if (connectionIdClaim == null)
{
context.Fail();
return;
}
var connectionId = connectionIdClaim.Value;
await _hubContext.Clients.Client(connectionId).SendAsync("message", "You are not authorized to perform this action.");
context.Fail();
}

Response from http post request gets cut in the middle

I'm trying to connect to a webservice that provides some customer data through a POST request but the response gets cut in the middle (or it might be that the trigger function doesn't await the response to complete).
This is done in a flutter environment and the initState() triggers the request.
I have a data service for the customer stuff, CustomerDataService which extends DataService that contain some common stuff such as sending the request and so on.
So in short initState() invoke CustomerDataService.getCustomers(request) which in turn invokes and await DataService.post(endpoint, request).
Http-package: import 'package:http/http.dart' as http;
initState() which is the starting point:
final CustomerDataService _dataService =
new CustomerDataServiceProvider().getCustomerDataService();
#override
void initState() {
_getCustomers();
super.initState();
}
void _getActors() async {
_dataService.getCustomers(
request: new GetCustomersRequest(
navigations: _dataService.suggestedNavigations
),
).then((response) {
_customersResponse = response;
/// Set the state
refresh();
});
}
And then we have the CustomerDataService:
class _CustomerDataService extends DataService implements CustomerDataService
#override
Future<GetCustomersResponse> getCustomers(
{#required GetCustomersRequest request}) async {
String endpoint = createEndpoint(<String>[
App.appContext.identityInstance.rootUrl,
_CUSTOMERS_CONTROLLER,
_GET_CUSTOMERS_ENDPOINT
]);
http.Response res = await post(endpoint: endpoint, request: request.toJson());
if (res.body == null) {
return null;
}
try {
/// This prints an invalid JSON that is cut in the middle
print(res.body);
/// This one naturally throws an exception since res.body isn't valid.
dynamic json = jsonDecode(res.body);
return new GetCustomersResponse.fromJson(json);
} catch (e) {
print("Exception caught when trying to get customers");
print(e);
}
return null;
}
The exception from jsonDecode is
Bad state: No element
And then we have the DataService:
Future<http.Response> post(
{#required String endpoint,
Map<String, String> header,
Map<String, dynamic> request}) async {
if (header == null) {
header = _getHeader();
} else {
header.putIfAbsent(_AUTHORIZATION_KEY, () => _headerAuthorizationValue());
}
http.Response res = await http.post(Uri.parse(endpoint), headers: header);
_validateReponse(res);
return res;
}
I'm clearly doing something wrong but I just can't see it...
The request in DataService.post doesn't add the body (request parameter) in this code and that is another ticket i will file after I've looked more into it, the workaround for now is to change the service to not expect a body.
I've verified that the service behaves as expected with postman.
I hope someone can see where my error(s) is.
Thanks!
Edit 1:
I changed the code a bit so that initState() doesn't use the DataServices created by me but used the http-package directly.
http.post('http://localhost:50140/api/customer/getcustomers').then((res) {
if(res == null) {
print('Response is empty');
}
print('Status code ${res.statusCode}');
print(res.body);
});
super.initState();
}
And the exact same thing happens so I don't think this is due to the dataservices at least.
Edit 2:
Before someone digs to deep into this I just want to say that it doesn't seem to be the response from the service, the http package or the dataservices.
This blog will be updated as soon as I find the cause of the Bad state: no element exception.
Okay, I don't know how to do this but it turns out the question title is incorrect.
It's the terminal that cuts the text when it's too big...
There were no errors in the dataservices or the http package but rather in the conversion from the response body to my strongly typed model, deep down the model tree.
An associative property to the Customer model have an enum, both server- and client side.
The service serialize the enum with the index, the library I use for mapping tries to get the enum by name (case sensitive).
The entity with the problem
#JsonSerializable()
class Item extends Object with _$ItemSerializerMixin
The auto-generated mapping
json['itemType'] == null
? null
: /// I could probably just remove the singleWhere and add [(int)json['itemType']] instead but that would cause some hassle when I run build_runner again.
ItemType.values
.singleWhere((x) => x.toString() == "ItemType.${json['itemType']}")
So, as soon as I did some changes server side (ignored the serialization of the enum and added another property which returned the enum string value instead, to lower case) it started working. I want to look into this further so that I can serialize the enum index instead of the string value and have it mapped that way instead but unfortunately I don't have the time to do it now.
The packages used for the auto-mapping is build_runner and json_serializable.
I'm glad I found a solution, and I'm sorry that the solution turned out to be completely unrelated to the actual post. I hope this can help someone at least.

How I can safely run async code, when handling messages from Messenger?

I am using MVVM, inparticular MVVMLight. For boradcasting to all of my modelviews, that no internet connection is available I am using Messenger class. The modelviews subscribe to this event in order to reload itself with offline data, inform user etc.
However, I have a problem. When I have the folowing handler:
private void HandleNoInternetMessage(NoInternetAccessMessage obj)
{
Task.Run(async () => await InitializeForOfflineInternalAsync());
}
public async Task InitializeForOfflineInternalAsync()
{
try
{
WaitingLayerViewModel.ShouldBeVisible = true;
WaitingLayerViewModel.IsBusy = true; //<--exception HRESULT: 0x8001010E (RPC_E_WRONG_THREAD)
bool switchToOffline = await CommonViewModelProvider.InformUserOfNoInternetAccessAndChangeAppState(); //<!- CoreWindow.GetForCurrentThread().Dispatcher is null
await FilterTestItemViewModel.InitializeForOfflineAsync();
await FilterTestItemViewModel.InitializeForOfflineAsync();
WaitingLayerViewModel.ShouldBeVisible = false;
WaitingLayerViewModel.IsBusy = false;
...
}
}
I got exception HRESULT: 0x8001010E (RPC_E_WRONG_THREAD), because in InitializeForOfflineInternalAsync I am changing some properties of the viewmodel wchich are bound in XAML (or at least I think it is because of that). However, it is weird, because I am changing in other code bound properties regularly and have no problems with it (and the thread is a working thread).
Now, how can i solve that?
The messanger let me provide only delegate which is not async (which make kind of sense), so I can not have the HandleNoInternetMessage method async
I am using async await ... no explicit spawning of threads
I dont have access in VM to Dispatcher, because I am in VM which should not know about platform dependent stuff. And when I tried to use it to show a message, NullPointer excpetion was thrown when calling CoreWindow.GetForCurrentThread().Dispatcher; And again when calling from other places, no such exception was thrown
I guess the question is How I can safely run async code, which changes boudn properties, when handling messages from Messenger?
You're responding to messages that are logically events, so this is an acceptable use case for async void.
private async void HandleNoInternetMessage(NoInternetAccessMessage obj)
{
await InitializeForOfflineInternalAsync();
}
public async Task InitializeForOfflineInternalAsync()
{
try
{
WaitingLayerViewModel.ShouldBeVisible = true;
WaitingLayerViewModel.IsBusy = true;
bool switchToOffline = await CommonViewModelProvider.InformUserOfNoInternetAccessAndChangeAppState();
await FilterTestItemViewModel.InitializeForOfflineAsync();
await FilterTestItemViewModel.InitializeForOfflineAsync();
WaitingLayerViewModel.ShouldBeVisible = false;
WaitingLayerViewModel.IsBusy = false;
...
}
}
Remember that Task.Run is for CPU-bound code (as I describe on my blog).

Asp.net Web API: HttpClient Download large files breaks

I have a web service (made in Asp.net Web API) that returns an xml file of about 10MB size.
The service has been tested with Fiddler and it is working
I am trying to download the file using HttpClient class. The problem is that the compilator never gets outside the await client.GetAsync() method, even if the API project returned the HttpResponseMessage.
This is my function
public async Task<XDocument> DownloadXmlAsync(string xmlFileName)
{
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:51734/");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/xml"));
// When the copiler enters this next command, it doesn't get outside anymore
HttpResponseMessage response = await client.GetAsync("api/applications/ApplicationXml?fileName=" + xmlFileName);
response.EnsureSuccessStatusCode();
string stringResponse = await response.Content.ReadAsStringAsync();
XDocument xDoc = new XDocument(stringResponse);
return xDoc;
}
}
I updated also the maxRequestLength in web.config
<httpRuntime maxRequestLength="15360" />
What i am doing wrong?
Edit
Calling the function
public async Task<ActionResult> Index()
{
var xmlTask = DownloadXmlAsync("1.xml");
// doesn't reach here
var result = xmlTask.Result;
return View();
}
You're causing a classic deadlock by calling Result. Instead, you should await the task:
public async Task<ActionResult> Index()
{
var xmlTask = DownloadXmlAsync("1.xml");
// doesn't reach here
var result = await xmlTask;
return View();
}
I explain this deadlock in full on my blog, but the general idea is like this:
ASP.NET only allows one thread to be processing a request at a time.
When you await a Task, the compiler will capture a "context" and use it to resume the method when the Task completes. In the case of ASP.NET, this "context" is a request context.
So when DownloadXmlAsync (asynchronously) waits for GetAsync to complete, it returns an incomplete task to Index.
Index synchronously blocks on that task. This means the request thread is blocked until that task completes.
When the file is received, GetAsync completes. However, DownloadXmlAsync cannot continue because it's trying to resume that "context", and the "context" already has a thread in it: the one blocked on the task.
Hence, deadlock.

Why is the body of a Web API request read once?

My goal is to authenticate Web API requests using a AuthorizationFilter or DelegatingHandler. I want to look for the client id and authentication token in a few places, including the request body. At first it seemed like this would be easy, I could do something like this
var task = _message.Content.ReadAsAsync<Credentials>();
task.Wait();
if (task.Result != null)
{
// check if credentials are valid
}
The problem is that the HttpContent can only be read once. If I do this in a Handler or a Filter then the content isn't available for me in my action method. I found a few answers here on StackOverflow, like this one: Read HttpContent in WebApi controller that explain that it is intentionally this way, but they don't say WHY. This seems like a pretty severe limitation that blocks me from using any of the cool Web API content parsing code in Filters or Handlers.
Is it a technical limitation? Is it trying to keep me from doing a VERY BAD THING(tm) that I'm not seeing?
POSTMORTEM:
I took a look at the source like Filip suggested. ReadAsStreamAsync returns the internal stream and there's nothing stopping you from calling Seek if the stream supports it. In my tests if I called ReadAsAsync then did this:
message.Content.ReadAsStreamAsync().ContinueWith(t => t.Result.Seek(0, SeekOrigin.Begin)).Wait();
The automatic model binding process would work fine when it hit my action method. I didn't use this though, I opted for something more direct:
var buffer = new MemoryStream(_message.Content.ReadAsByteArrayAsync().WaitFor());
var formatters = _message.GetConfiguration().Formatters;
var reader = formatters.FindReader(typeof(Credentials), _message.Content.Headers.ContentType);
var credentials = reader.ReadFromStreamAsync(typeof(Credentials), buffer, _message.Content, null).WaitFor() as Credentials;
With an extension method (I'm in .NET 4.0 with no await keyword)
public static class TaskExtensions
{
public static T WaitFor<T>(this Task<T> task)
{
task.Wait();
if (task.IsCanceled) { throw new ApplicationException(); }
if (task.IsFaulted) { throw task.Exception; }
return task.Result;
}
}
One last catch, HttpContent has a hard-coded max buffer size:
internal const int DefaultMaxBufferSize = 65536;
So if your content is going to be bigger than that you'll need to manually call LoadIntoBufferAsync with a larger size before you try to call ReadAsByteArrayAsync.
The answer you pointed to is not entirely accurate.
You can always read as string (ReadAsStringAsync)or as byte[] (ReadAsByteArrayAsync) as they buffer the request internally.
For example the dummy handler below:
public class MyHandler : DelegatingHandler
{
protected override async System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
var body = await request.Content.ReadAsStringAsync();
//deserialize from string i.e. using JSON.NET
return base.SendAsync(request, cancellationToken);
}
}
Same applies to byte[]:
public class MessageHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var requestMessage = await request.Content.ReadAsByteArrayAsync();
//do something with requestMessage - but you will have to deserialize from byte[]
return base.SendAsync(request, cancellationToken);
}
}
Each will not cause the posted content to be null when it reaches the controller.
I'd put the clientId and the authentication key in the header rather than content.
In which way, you can read them as many times as you like!

Resources