Use MVC's model binding from application code - asp.net-core-mvc

As a sample of what I'm trying to accomplish, here in MapPost I'm manually parsing the body of the HTTP request.
// Program.cs
using System.Text.Json;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
Type[] types = new[] { typeof(SampleDto1), typeof(SampleDto2), <other unknown types> };
foreach (var type in types)
{
app.MapPost(type.Name, async (HttpContext httpContext) =>
{
var request = await JsonSerializer.DeserializeAsync(
httpContext.Request.Body,
type,
new JsonSerializerOptions(JsonSerializerDefaults.Web),
httpContext.RequestAborted);
return Results.Ok(request);
});
}
app.Run();
internal record SampleDto1(string Input) { }
internal record SampleDto2(string Input) { }
This works, yay! However, ... ASP.NET Core's MVC has all these sophisticated ModelBinding functionality and I really would like to use that. Because that opens up possibilities for binding to querystring parameters and other sources instead of only the request body.
Basically I want to replace the call to JsonSerializer with a call to framework code.
I've been browsing the ASP.NET Core source code and at first the DefaultModelBindingContext looked promising. However, I soon stumbled on some internal classes which I couldn't access from my code.
Long story short, .. is it at all possible to plug-in to MVC's model binding from application code?
Update: Although it doesn't show from the initial question, the solution should work dynamically with any request type. Not only SampleDto1 and SampleDto2. That's why explicit parameter binding from Minimal API won't do the trick.

You could try the codes :
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
builder.Services.AddSingleton<Service>();
app.MapPost("/{id}", ([FromRoute] int id,
[FromQuery(Name = "p")] int page,
[FromBody]SampleDto1 sample1,
[FromBody] SampleDto2 sample2,
[FromServices] Service service,
[FromHeader(Name = "Content-Type")] string contentType)
=> { });
app.Run();
internal record SampleDto1(string Input) { }
internal record SampleDto2(string Input) { }
You could read the official document for more details:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis?view=aspnetcore-6.0#explicit-parameter-binding

Related

Client-Side error when uploading image on server ASP.NET Core

I am struggling with uploading an image from thew client-side to a folder on the server-side in .Net Core.I used Postman to check if the method on the server-side is working and it does without any problem,but when I try to upload an image from the client-side,I get an error on the server-side of type NullReferenceException:Object reference not set to an instance of an object.This is the Post method on the server-side:
[HttpPost]
public async Task Post(IFormFile file)
{
if (string.IsNullOrWhiteSpace(_environment.WebRootPath))
{
_environment.WebRootPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot");
}
var uploads = Path.Combine(_environment.WebRootPath, "uploads");
//var fileName = file.FileName.Split('\\').LastOrDefault().Split('/').LastOrDefault();
if (!Directory.Exists(uploads)) Directory.CreateDirectory(uploads);
if (file.Length > 0)
{
using (var fileStream = new FileStream(Path.Combine(uploads, file.FileName), FileMode.Create))
{
await file.CopyToAsync(fileStream);
}
}
}
Apparently the method is thrown where I check if the length of the file is bigger than 0.On the client-side I get error "500 internal server error" and I tried to check using the debugger where exactly the error is thrown but i can't find anything that could resemble an error of some sort.This is the API method for the client-side:
public async Task UploadPictureAsync(MediaFile image)
{
User user = new User();
string pictureUrl = "http://10.0.2.2:5000/api/UploadPicture";
HttpContent fileStreamContent = new StreamContent(image.GetStream());
// user.Picture=GetImageStreamAsBytes(image.GetStream());
fileStreamContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") {FileName=Guid.NewGuid() + ".Png",Name="image"};
fileStreamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
HttpClientHandler clientHandler = new HttpClientHandler();
clientHandler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { return true; };
using (var client = new HttpClient(clientHandler))
{
using (var formData = new MultipartFormDataContent())
{
formData.Add(fileStreamContent);
var response = await client.PostAsync(pictureUrl, formData);
if(response.IsSuccessStatusCode)
{
var result = response.Content.ReadAsStringAsync().Result;
}
}
}
}
The image is declared in the Model as byte array:
public byte[] Picture { get; set; }
Does someone understand why my POST method has this behavior since the server-side works perfectly but fails when I try to upload an image from the client-side?What I find weird though is that when i read the error and I look at the Content-Type it is "text/plain" instead of "form-data" and I have tried to set it at the MutipartFormDataContent like this:
formData.Headers.ContentType.MediaType = "multipart/form-data";
I also tried to set the MediaTypeHeaderValue on the client like this:
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/octet-stream"));
I still get the wrong content type.
I have also tried a different approach with Stream instead of MediaFile but without any luck as it did not even hit the break point in debugger mode for the response.Any help would be appreciated! :)
I have managed to find the answer finalllyyyyy!!!The problem was on the client-side as I suspected and guess what,it was all about the correct name.It turns out that since on the server side I have IFormFile file I had to change the client side to take the parameter name "file" instead of image as well so that it could work.Thank you #Jason for the suggestions as I didn't understand the error from the first place and did some debugging on the server-side to help me figure it out.

Xamarin.Form HttpClient for WebAPI failed in iOS

I'm creating Xamarin.Forms for Android and iOS using WebAPI as the web service. The whole thing went well in Android but I hit error in iOS, particularly when doing "JsonConvert.DeserializeObject". Below is the sample code
Model
public class WsObjTest
{
public string name { get; set; }
public string code { get; set; }
public string age { get; set; }
}
WebAPI
[HttpGet]
public WsObjTest HelloWorld()
{
WsObjTest wsObjTtest = new WsObjTest();
wsObjTtest.name = "John Doe";
wsObjTtest.code = "ABC123";
wsObjTtest.age = "18";
return wsObjTtest ;
}
In my Xamarin.Forms, this is how I call and deserialize the response
HttpClient client = new HttpClient();
var response = await client.GetStringAsync(apiURL.Replace("##action##", "HelloWorld"));
return JsonConvert.DeserializeObject<WsObjTest>(response); //ERROR HERE
I got the response as below, but error thrown at the return statement.
"{\"code\":\"ABC123\",\"name\":\"John Doe\",\"age\":\"18\"}"
The error message is below
Unhandled Exception:
System.MemberAccessException: Cannot create an abstract class:
System.Reflection.Emit.DynamicMethod occurred
I think it's because iOS doesn't support JIT compilation or dynamic methods of some sort? May I know if there is anyway to overcome this error? Thanks.
Based on https://learn.microsoft.com/en-us/xamarin/ios/internals/limitations, it is not possible to use any facilities that require code generation at runtime in Xamarin.iOS because code on the iPhone is statically compiled ahead of time instead of being compiled on demand by a JIT compiler.
What I have to do now is read and create the object manually as below. Should anybody got better solution please share with me.
HttpClient client = new HttpClient();
var response = await client.GetStringAsync(apiURL.Replace("##action##", "HelloWorld"));
JObject jObject = (JObject) JsonConvert.DeserializeObject(response);
WsObjTest wsObjTest = new WsObjTest();
wsObjParent.name = jObject["name"].ToString();
wsObjParent.code = jObject["code"].ToString();
wsObjParent.age = jObject["age"].ToString();
return wsObjTest;
Extracted from the link
Since the iOS kernel prevents an
application from generating code dynamically, Xamarin.iOS does not
support any form of dynamic code generation. These include:
The System.Reflection.Emit is not available.
No support for
System.Runtime.Remoting.
No support for creating types dynamically (no
Type.GetType ("MyType`1")), although looking up existing types
(Type.GetType ("System.String") for example, works just fine).
Reverse
callbacks must be registered with the runtime at compile time.
So the System.Reflection.Emit thus the error that I received.

Globally formatting .net Web Api response

I have a Web Api service that retrieves data from another service, which returns Json. I don't want to do anything to the response, I just want to return it directly to the client.
Since the response is a string, if I simply return the response, it contains escape characters and messy formatting. If I convert the response in to an object, the WebApi will use Json.Net to automatically format the response correctly.
public IHttpActionResult GetServices()
{
var data = _dataService.Get(); //retrieves data from a service
var result = JsonConvert.DeserializeObject(data); //convert to object
return Ok(result);
}
What I would like is to either A: Be able to return the exact string response from the service, without any of the escape characters and with the proper formatting, or B: Set a global settings that will automatically Deserialize the response so that the Web Api can handle it the way I am doing it already.
On Startup I am setting some values that describe how formatting should be handled, but apparently these aren't correct for what im trying to do.
HttpConfiguration configuration = new HttpConfiguration();
var settings = configuration.Formatters.JsonFormatter.SerializerSettings;
settings.Formatting = Formatting.Indented;
settings.ContractResolver = new DefaultContractResolver();
Do I need to create a custom ContractResolver or something? Is there one that already handles this for me?
Thanks
If you want to just pass through the json (Option A), you can do this
public IHttpActionResult GetServices() {
var json = _dataService.Get(); //retrieves data from a service
HttpContent content = new System.Net.Http.StringContent(json, Encoding.UTF8, "application/json");
var response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = content;
return ResponseMessage(response);
}

ASync Recaptcha in MVC3

I've implemented ReCaptcha in MVC3 using ReCaptcha.net NuGet package http://recaptchanet.codeplex.com/wikipage?title=How%20to%20Use%20Recaptcha%20in%20an%20ASP.NET%20MVC%20Web%20Application. All working well, except I'd like to see if I can implement this as Async as it is sometimes quite slow, and we may have some volume on these pages.
The instructions say
RecaptchaVerificationResult recaptchaResult = await recaptchaHelper.VerifyRecaptchaResponse();
if (recaptchaResult != RecaptchaVerificationResult.Success)
{
ModelState.AddModelError("", "Incorrect captcha answer.");
}
however, this is using the MVC4 await syntax. Is there a way I can use this method within the MVC3 async framework?
I did try a quick hack, converting the controller to AsyncController naming the method with an Async suffix and wrapping the entire action in a Task.Factory.StartNew(() => { ... }); while using the non-async syntax, but RecaptchaVerificationHelper recaptchaHelper = this.GetRecaptchaVerificationHelper(); complains about a lack of HTTPContext.
So, can anyone help me with doing ReCaptcha asynchronously in MVC3
In the end, I've dropped using the NuGet package, and simply process the captcha's using the code below, binding the recaptcha fields in the controller method.
public bool ProcessCaptcha(string recaptcha_challenge_field, string recaptcha_response_field)
{
const string verifyUrl = "http://www.google.com/recaptcha/api/verify";
var res = true;
var ip = Request.UserHostAddress;
if (ip == "::1") ip = "127.0.0.1";
var myParameters = string.Format("privatekey={0}&remoteip={1}&challenge={2}&response={3}", Config.CaptchPriv, ip, recaptcha_challenge_field, recaptcha_response_field);
using (WebClient wc = new WebClient())
{
wc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
string HtmlResult = wc.UploadString(verifyUrl, myParameters);
var split = HtmlResult.Split('\n');
if (split[0] == "false") res = false;
}
return res;
}
With this in place, I split my original controller method into a Async/Completed pair, and wrapped the work it does in Task.Factory.StartNew(() => { ... }), following the pattern outlined here http://www.deanhume.com/Home/BlogPost/mvc-asynchronous-controller---the-basics/67 which seems to work perfectly.

MVC3 unit testing response code

I have a controller within MVC3 which needs to return a response code 500 if something goes wrong. I am doing this by returning a view object and setting http response code to equal 500 (I have checked this in firebug and all is working great).
public ActionResult http500()
{
ControllerContext.HttpContext.Response.StatusCode = 500;
ControllerContext.HttpContext.Response.StatusDescription = "An error occurred whilst processing your request.";
return View();
}
The problem I have now is I need to be able to write a unit test which checks the response code. I have tried accessing the response code in several different ways both through the ViewResult object and the Controller context.
Neither way gives me the response code I have set in the controller.
[TestMethod()]
public void http500Test()
{
var controller = new ErrorController();
controller.ControllerContext = new ControllerContext(FakeHttpObject(), new RouteData(), controller);
ViewResult actual = controller.http500() as ViewResult;
Assert.AreEqual(controller.ControllerContext.HttpContext.Response.StatusCode, 500);
}
How would I go about getting the response code 500 from the controller or is this more of an integration testing thing.
How about doing it in a more MVCish way:
public ActionResult Http500()
{
return new HttpStatusCodeResult(500, "An error occurred whilst processing your request.");
}
and then:
// arrange
var sut = new HomeController();
// act
var actual = sut.Http500();
// assert
Assert.IsInstanceOfType(actual, typeof(HttpStatusCodeResult));
var httpResult = actual as HttpStatusCodeResult;
Assert.AreEqual(500, httpResult.StatusCode);
Assert.AreEqual("An error occurred whilst processing your request.", httpResult.StatusDescription);
or if you insist on using the Response object you could create a fake one:
// arrange
var sut = new HomeController();
var request = new HttpRequest("", "http://example.com/", "");
var response = new HttpResponse(TextWriter.Null);
var httpContext = new HttpContextWrapper(new HttpContext(request, response));
sut.ControllerContext = new ControllerContext(httpContext, new RouteData(), sut);
// act
var actual = sut.Http500();
// assert
Assert.AreEqual(500, response.StatusCode);
Assert.AreEqual("An error occurred whilst processing your request.", response.StatusDescription);
What is FakeHttpObject()? Is it a mock created using Moq? In that case you need to setup setters and getters to store the actual values somewhere. Mock<T>doesn't provide any implementation for properties and methods. When setting a value of property literally nothing happens and the value is 'lost'.
Another option is to provide a fake context that is a concrete class with real properties.

Resources