How to pass data from component to controller? - c#

Pass a string variable from the front end (HTML/.ts to C# controller).
I'm skimming through angular.io documentation and walkthroughs made by unofficial people. I watched videos and it appears none of it is relevant to my code. I started a new project(ASP.NET Core web application with Angular) in Visual Studio 2019. There are .ts components and .cs controllers. My HTML is set up to take a string input. I have tried using HTTP POST request and ajax requests. I may have done them incorrectly with the wrong arguments. I've consulted
How to send data to an ASP.NET Controller with Angular2
ASP.Net MVC How to pass data from view to controller
Passing Model data from View to Controller and using its values
AngularJS & asp.net MVC - passing data to controller?
and countless others not on StackOverflow.
.html
<input #box (keyup)="onKey(box.value)">
Your name is: {{name}}
.ts
onKey(value: string)
{
this.name = value;
}
I expect the name string to be available in the controller where I can print it out and later use it in a database.

I create a demo using asp.net core Angular template.It passes data on Keyup.
1.home.component.html
<input #box (keyup)="onKey(box.value)">
Your name is: {{name}}
2.home.component.ts
import { Component } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
})
export class HomeComponent {
name: string;
constructor(
private http: HttpClient
) { }
onKey(value: string):void {
this.name = value;
const formData: FormData = new FormData();
formData.append('name', this.name);
this.http.post('https://localhost:44336/api/SampleData/TestName', formData).subscribe(result => {
console.log(result);
}, error => console.error(error));
}
}
3.SampleData controller(/api/sampleData)
[HttpPost("TestName")]
public JsonResult TestName(string name)
{
//your logic
return Json(name);
}

As per official guide line: https://angular.io/guide/user-input
<input (keyup)="onKey($event)">
onKey(event: any) {
console.log(event.target.value);
let inputValue = event.target.value;
}
Now inputValue will store your value from Input Field on Keyup Event.

Related

asp.net core 3.1 streaming large file upload - can't prevent model binding

=============== UPDATE ===============
Apologies, but with all the chopping and changing, commenting out and back in again, to try and get this working, I had accidentally stopped the form calling the script in Create.cshtml. The form of the View file should actually read as follows:
<form asp-controller="Documents" asp-action="Create" method="post" enctype="multipart/form-data" onSubmit="AJAXSubmit(this); return false;">
I haven't closed the question, because the script in Create.cshtml is receiving a 400 (Bad Request). This appears to be the real problem - but, I cannot understand why it's happening.
If and when I get to the bottom of it, I will update the question. Hopefully, it will be a useful answer to somebody.
=============== END UPDATE ===============
I've been following https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-3.1#upload-large-files-with-streaming to upload large files to a web app. Unfortunately, when I come to use the MultipartReader (reader.ReadNextSectionAsync), I get the error unexpected end of stream, the content may have already been read by another component.
I've downloaded the sample app (https://github.com/dotnet/AspNetCore.Docs/tree/master/aspnetcore/mvc/models/file-uploads/samples/3.x/SampleApp) and that works fine, but they're using Razor pages and my project isn't.
One notable difference: when running the demo app from github, when I click Upload, to upload the large file, the Controller method is called immediately. However, when I click to upload the document on my app, the browser shows me the file being uploaded, then the Controller method is called.
I'm fairly new to ASP.NET, and I've been banging my head against a brick wall for a couple of days, so any help would be much appreciated.
My relevant code snippets are as follows:
GenerateAntiforgeryTokenCookieAttribute: adds antiforgery token to cookie, copied exactly from the example docs
DisableFormValueModelBindingAttribute: attribute class to prevent form binding, copied exactly from the example docs
MultipartRequestHelper: copied exactly from the example docs
startup.cs: this is maybe where my problem is, as my project is not using Razor pages, and is using Feature folders, so I've deviated from the example. Plus, my project is using Feature folders.
public void ConfigureServices(IServiceCollection services)
{
...
services.AddMvc(o => o.Conventions.Add(new FeatureConvention()))
// first part is for feature folders
.AddRazorOptions(options =>
{
options.ViewLocationFormats.Clear();
options.ViewLocationFormats.Add("/Features/{3}/{1}/{0}.cshtml");
options.ViewLocationFormats.Add("/Features/{3}/{0}.cshtml");
options.ViewLocationFormats.Add("/Features/Shared/{0}.cshtml");
options.ViewLocationExpanders.Add(new FeatureViewLocationExpander());
})
// FeatureConvention and FeatureViewLocationExpander taken from https://learn.microsoft.com/en-us/archive/msdn-magazine/2016/september/asp-net-core-feature-slices-for-asp-net-core-mvc#feature-folders-in-aspnet-core-mvc
// next part is to prevent form value binding and to add the anti forgery token to the cookie
.AddRazorPagesOptions(options =>
{
options.Conventions.AddPageApplicationModelConvention(
"/Documents/Create",
model =>
{
model.Filters.Add(new GenerateAntiForgeryTokenCookieAttribute());
model.Filters.Add(new DisableFormValueModelBindingAttribute());
}
);
});
...
}
My folder structure is as follows:
root
|
`-- Features
|
`-- Documents
|
`-- Create.cshtml
`-- DocumentsController.cs
Create.cshtml
#model DocumentCreateViewModel
<form asp-controller="Documents" asp-action="Create" method="post" enctype="multipart/form-data">
<div>
<label asp-for="filename"></label>
<input asp-for="filename""><input/>
</div>
<div>
<label asp-for="file"></label>
<input asp-for="file"><input/>
</div>
<div>
<button type="submit">submit</button>
</div>
</form>
#section Scripts {
<script>
"use strict";
async function AJAXSubmit (oFormElement) {
const formData = new FormData(oFormElement);
try {
const response = await fetch(oFormElement.action, {
method: 'POST',
headers: {
'RequestVerificationToken': getCookie('RequestVerificationToken')
},
body: formData
});
oFormElement.elements.namedItem("result").value =
'Result: ' + response.status + ' ' + response.statusText;
} catch (error) {
console.error('Error:', error);
}
}
function getCookie(name) {
var value = "; " + document.cookie;
var parts = value.split("; " + name + "=");
if (parts.length == 2) return parts.pop().split(";").shift();
}
</script>
}
DocumentCreateViewModel.cs
public class DocumentCreateViewModel
{
[Required]
public string Name { get; set; }
[Required]
public IFormFile File { get; set; }
}
DocumentsController.cs: is the contents of UploadPhysical, copied exactly from the example docs, UploadPhysical is renamed to Create
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error
return BadRequest(ModelState);
}
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
// ^^^^ this is where the "unexpected end of stream" exception is thrown
Firstly, thank you to #Brando Zhang for getting me back on track.
The problem, the 400 (Bad Request), was due to DocumentsController attempting to verify an anti-forgery token that didn't exist - which was down to me unsuccessfully porting the Razor example into MVC.
In the example code, in startup.c, the following section is only applicable to Razor pages - not MVC:
.AddRazorPagesOptions(options =>
{
options.Conventions.AddPageApplicationModelConvention(
"/Documents/Create",
model =>
{
model.Filters.Add(new GenerateAntiForgeryTokenCookieAttribute());
model.Filters.Add(new DisableFormValueModelBindingAttribute());
}
);
});
Therefore, that could be removed. However, now there is not anti-forgery token being generated, so when a POST is made to Create() in DocumentsController, no token is present, so an HTTP 400 is returned.
The solution is a simple one (although it took me long enough to find it!): add [GenerateAntiForgeryTokenCookie] to the GET method that presents the form in DocumentsController:
[HttpGet]
[GenerateAntiForgeryTokenCookie]
public IActionResult Index()
{
return View("Create");
}
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
...
}
Hope it helps!

Using React state as a parameter in .NET backend function

I am exploring writing single page applications with React on the front end and .NET on the backend (and I am new to both, so apologies if the questions seem simple!). I am using Visual Studio for Mac.
In order to start simple, all my backend code does is returns "Hello, (name of person)" e.g. "Hello, Bob". However, I want the name of the person to be whatever a user inputs into the React form on the web page.
.NET Controller:
namespace TestingReactDotNet.Controllers
{
[Route("api/[controller]")]
public class DisplayNameController : Controller
{
[HttpGet, Route("Greeting")]
public string Greeting(string name)
{
var greeting = "Hello" + name;
return greeting;
}
}
}
React file (DisplayName.js):
import React, { Component } from "react";
export class DisplayName extends Component {
state = {
name: "",
greeting: ""
};
updateInput(key, value) {
this.setState({
[key]: value
});
}
calculate(){
fetch("api/DisplayName/Greeting")
.then(response => response.text())
.then(data => {
this.setState({ greeting: data });
});
<h1>this.state.greeting</h1>
}
render() {
return (
<center>
<div className="App">
<div>
Type a word...
<br />
<input
type="text"
placeholder="Type word here ..."
value={this.state.name}
onChange={e => this.updateInput("name", e.target.value)}
/>
<button onClick={() => this.calculate()}>Submit</button>
<br />
</div>
</div>
</center>
);
}
}
My question is:
How do I get the frontend and backend to talk to each other here, so that whatever name the user inputs into the form, the DisplayName method can use in order to return the string which is then displayed on the page?
I appreciate it's probably easier just to do this using React alone, but I am really trying to ensure that I can use React and .NET together, so any advice is really appreciated.
Thanks :)
Your code is excellent. But there is only one problem: when you got the result from the server, you do it nothing!
Look at your calculate() method:
calculate(){
fetch("api/DisplayName/Greeting")
.then(response => response.text())
.then(data => {
this.setState({ greeting: data });
});
<h1>this.state.greeting</h1>
}
What does the last line do? It creates a meaningless element that won't appear in any place! (also, you forgot the curly braces around this.state.greeting).
Instead, you should use the following approach: render the form if the it's not submitted yet, otherwise render the answer (it's just an example; you can render the both (if available) and more):
calculate(){
fetch("api/DisplayName/Greeting")
.then(response => response.text())
.then(data => {
this.setState({ greeting: data });
});
}
render() {
if ('' !== this.state.greeting) {
// We already submitted the form, show welcome message
return <h1>{this.state.greeting}</h1>
} else {
// As your code
// ...
}
}
Edit:
After your comment, I noticed why your server doesn't receive the name: it's simple - you don't send it!
So, How To Send Parameters To ASP.NET Backend With The Fetch API?
I'll discuss it shortly; it is very wide subject. I you want a comprehesive explanation, google for "ASP.NET Core Model Binding" and "js fetch api".
Let's start with the client side:
There are some ways to send data using fetch(): the simplest are query string (http://...?w=x&y=z) and using the body option, which accepts string that may be json, for example.
But the body parameter not works for get request. So, we'll use query string. Your case is simple enough to simply concatenate the strings, for more complex cases, see Setting query string using Fetch GET request.
Update your client code as follows:
// ...
fetch('/api/DisplayName/Greeting?name=' + encodeURIComponent(this.state.name))
// ...
(We're using encodeURIComponent() which encodes a string to be inside the query string).
In the server side, this is automatically, very luickly!
But to learn: what's happen?
In ASP.NET, there is a concept called Model Binding: The ability to take parameters from the request and make them parameter of the controller method.
There are some builtin model binders in ASP.NET (you can build your own, but in most cases you don't), our important is:
1) Query string model binder, which takes the query string parameters and pass them as arguments with the same name to the controller.
2) JSON model binder, which if the content-type is json, parses it and pass it too.
There are much more model binders.
These binders can handle also arrays (including the query string (in special form)!), nested object properties (which can lead to overposting attack) and more.
So, the only thing you need is to pass the parameter, then the model binder will do all the work automatically for you!

ASP.Core Uploading image with Axios

I am trying to upload an image to a server with Axios, but having Error 400 "Bad Request".
I have tried many different ways to implement it: explicitly specifying a "Content-Type", changing controller, trying $.ajax, etc. from Google.
A brief description of what I am expecting: I choose an image as suggested by <input/> and once I submit my choice it is sent to the server.
Currently, in my controller, I set a breakpoint, however, I've never seen request entering it.
My stack is Asp.Core WebAPI + React.js + Axios.
I have stopped on the following code:
Controller:
[Route("api/char")]
[ApiController]
public class CharacterController : ControllerBase
{
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> SetProfilePhoto(IFormFile file)
{
return Ok();
}
}
React Component:
export default class BioEdit extends React.Component {
...
changePhoto = event => {
event.preventDefault();
const file = event.target.files[0];
const formData = new FormData();
formData.append("File", file);
Axios.post("api/char/SetProfilePhoto", formData, {
headers: { 'Content-Type': 'multipart/form-data' }
})
.catch(error => console.log(error));
}
render(){
return (
<label >
<img src={this.state.char.image} width="30%" height="30%" className="border border-secondary" />
<input type="file" style={{ display: "none" }} accept="image/*" onChange={this.changePhoto} />
</label>
);
}
}
Here is what I have in the browser (Yandex browser it is to be specific, based on Chrome):
There are two options based on whether you need API Controller or not.
The first is to remove the APIControllerAttribute from off your controller, then nothing else is needed to be done;
The second is to set up the Startup.cs as doc prescribes. Adding
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); solved my problem.
NOTE: it is not necessary to set up headers to undefined or whatever. Axios deals with it in the right way itself.
add undefined content type. it should work then
Axios.post("api/char/SetProfilePhoto", formData, {
headers: { 'Content-Type': undefined }
})

How to post an object from MVC controller to Web Api controller?

Scenario is my MVC view is returning data to Controller action and from my action requirement is to build an object and pass it to an external Web API. I m getting data in my action and building an object as well. Can you please direct me how I should pass object to external Web API.
Also should it be JSON, object or xml ?
I m giving my controller and Web API code below:
Controller action:
public ActionResult Submit(FormCollection form)
{
Options lead = new Options();
lead.Situation = form.GetValue("InsuranceFor").AttemptedValue;
lead.State = form.GetValue("InsuranceState").AttemptedValue;
//Here I want to pass object to Web API
return RedirectToAction("Parameters");
}
Web API method:
public void Post(Lead_Options lead)
{
leadOptService.AddListOptions(lead);
}
I just completed a complex implementation just to satisfy similar requirement. I was assigned to post object from C# MVC Controller to an external RESTful Web API. In the future, the Web API will remain, but the C# MVC may be replaced with NodeJS / Angular application. So what I did was, assign the object to a TempData in a Serialized JSON format, then in the View where the page redirects to, conditionally added AngularJS, and implement AngularJS post to the external WebAPI. In your case, the TempData would look something like this:
this.TempData["lead"] = new JavaScriptSerializer().Serialize(this.Json(lead, JsonRequestBehavior.AllowGet).Data);
Then, in the redirected view "Parameters", you could add this angular code:
#if (this.TempData["lead"] != null)
{
<script type="text/javascript" src="#Url.Content("~/Contents/Scripts/angular.js")"></script>
<script type="text/javascript">
angular
.module('app', [])
.controller('controllerName', ['$http', '$scope', 'apiFactory', function ($http, $scope, apiFactory) {
var leadRecord = '#Html.Raw(this.TempData["lead"])';
var apiUrl = 'https://xxxxxxxxxxxxxx';
apiFactory({
method: 'POST',
url: apiUrl + '/api/apiControllerName/Post',
data: '=' + leadRecord,
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8' }
}).then(function (result) {
console.log(result);
});
}])
.factory('apiFactory', function ($http, $q) {
return function (config) {
var defered = $q.defer();
$http(config)
.success(function (result, status, headers, config) {
defered.resolve(result);
})
return defered.promise;
}
})
</script>
}
<div ng-app="app" class="col-sm-12 sign-in-page">
<div class="row" ng-controller="controllerName">
..... contents of redirected page ....
</div>
</div>
Your WebAPI - (Assuming it's C# Web API 2.2 should look something like this:
[HttpPost]
public string Post([FromBody]string jsonString)
{
try
{
IDictionary<string, string> data = JsonConvert.DeserializeObject<IDictionary<string, string>>(jsonString);
Assuming your object's values are all strings ....
This implementation may not be ideal but it does its job for sure
Oh, alternatively, you could simply add the angular POST to your original view that contains the form controls. But in my case this was not an option because the View must make a full post, the data from the full post must be processed in the model, then the controller gets some of the data from the models and combine it with session information to make up the object, which then has to be sent to a Web API controller..

How to make Angular POST to C# Asp.Net MVC Controller?

I have looked around, but have not found anything (Angular post) that can actually make a successful call to a MVC Controller. I know there are a lot of Angular/.Net devs out there. Can I get some help?
Let's keep answers bare bones simple!!!
If I set a linebreak on the controller, I can see that this code is not actually hitting the controller.
HTML
<!-- I click this button -->
<input type="button" value="click" onclick="postit()" />
Javascript/Angular Post
function postit() {
$http({
method: 'POST',
url: 'Home/Give/',
data: { id: 4 }
}).success(successFn).error(errorFn);
}
function successFn() {
alert("success");
}
MVC C# Controller
[AcceptVerbs("OPTIONS")]
public ActionResult Give(int id)
{
var response = "some response" + id.ToString();
return Json(new JavaScriptSerializer().Serialize(response));
}
king Puppy, I've seen a few responses that dictate that the controller parameters should be an object that matches the object that is being sent, however, it seems that it's a little more forgiving than that. Consider the following example (I've updated your function a little):
Javascript:
$scope.postIt = function() {
var data = {
id = 4
};
$http
.post('Home/Give', data)
.success(function(data, status, headers, config) {
successFn();
})
.errors(function(data, status, headers, config) {
errorFn();
});
};
function successFn() {
alert("success");
};
function errorFn() {
alert("error");
};
MVC:
public ActionResult Give(int id)
{
var response = "some response" + id.ToString();
return Json(new JavaScriptSerializer().Serialize(response));
}
If you set a breakpoint, you will see that the id passed in is 4.
If you needed to pass in an object (so more than just one id), you could either create a matching class or struct on the controller side, or have multiple parameters (provided that they are simple value types)
ie:
public JsonResult Give (int id, int reasonId)
{
...
}
Anyway, I realize the post is old, but perhaps it will help you or others.
#kingPuppy this is my way to how to make angularjs post to mvc controller
first, html button for passing the angular js button click function;
<button class="btn btn-info" id="runButton" ng-click="runService('Hi')">Run</button>
so runService angular click (ng-click) function;
// Operation Type is my mvc controller's param
$scope.runService = function (optionType) {
$http({
url: '/System/RunService',
method: "POST",
data: {operationType : optionType}
}).then(function onSuccess(response) {
// Handle success
console.log(response);
}).catch(function onError(response) {
// Handle error
console.log(response);
});
}
And finally this is system controller's action;
NOT : Dont forget to [HttpPost] attribute
[HttpPost]
public ActionResult RunService(string operationType)
{
// Codes
Response.StatusCode = 200;
return Json(JsonRequestBehavior.AllowGet);
}
Hope this could help to you for how to make angular post to mvc controller. Thanks.
There is nothing special you have to do to get Angular to post to a standard MVC controller, and in fact I have several Angular/MVC apps that are using code almost identical to what you have above to POST to controllers that work fine.
I would use Firebug to confirm that your app is posting to the right place. One thing I noticed is that you might not want that trailing / at the end of your URL (so Home/Give instead of Home/Give/)
Good luck!

Categories