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!
Related
I have a simple phonebook app that allows the user to search for an employee via a dropdown list of departments or by name via text input. The way I'm handling the search is simply changing the action attribute of the html form with javascript based on whatever they typed or whichever option they selected in the dropdown:
function selectedOption(option) {
var selValue = option.value;
document.getElementById("search-form").action = "/home/index/" + selValue;
}
This works on localhost but when I host it on IIS:
machineName/appName
becomes
machineName/home/index/selValue
cutting off the app name and returning a 404 error
The only way I've been able to get around this is with some hardcoding to check whether "home/index" exists in the path already...
function selectedOption(option) {
var selValue = option.value;
if (window.location.href.includes("home")) {
var searchDest = selValue
} else {
var searchDest = window.location.href + "/home/index/" + selValue
}
document.getElementById("search-form").action = searchDest;
}
This works but it is not very clean or conventional. Is there some way I can have the app name configured in Startup.cs? I saw the same issue in another question here but I'm getting syntax errors when I try to add another MapRoute. This is all that I currently have:
app.UseMvc(routes =>
{
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
Any idea how to get around this?
as mentioned above by #Desmond Chin, for cshtml you can use HTML helpers. I think the best one for that case is to use #Url.Action(actionName, controllerName) instead #Url.Content() - which is used for js, css, img, etc..
You can use this code bellow to achieve this:
function selectedOption(option) {
var selValue = option.value;
document.getElementById("search-form").action = '#Url.Action("Index", "Home")?<<VARIABLENAMEHERE>>'+selValue;
}
The Html Helper will take care of your url, virtual paths and etc
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.
I am a beginner and I am going through some tutorials in my MVC. So, I came across two scenarios.
Scenario 1.
I had to pass some data to my view on post and then send that data as hidden field. Here is the code.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ForgotPassword(ForgotPasswordMV viewModel)
{
if (ModelState.IsValid)
{
return RedirectToAction("VerifyToken", new { emailId = viewModel.EmailId });
}
^^ USING ANONYMOUS OBJECTS
return View();
}
public ActionResult VerifyToken(string emailId = null)
{
VerifyTokenMV viewModel = new VerifyTokenMV
{
EmailId = emailId
};
return View(viewModel);
}
VerifyToken View
#using (#Html.BeginForm("VerifyToken", "Security"))
{
#Html.HiddenFor(m => m.EmailId)
<button class="btn btn-primary">Continue</button>
}
Works Perfectly fine. I am able to receive values of EmailId. So far so good.
Scenario 2.
Needed to open a partial view from Main view, here is the snippet.
Main cshtml file
<div class="abc">
#Html.Partial("../Widget/Customize", Model.Unit, new ViewDataDictionary() { { "ElementName", "UnitWidget" } })
</div>
partial cshtml file
#{
string custWidgetElementName = ViewBag.ElementName;
}
// some html code below
Observation:
In scenario 2 why have I used ViewDataDictionary. Although both example works perfectly fine. But is there any reason that I had to use ViewDataDictionary. In scenraio 1 can we use ViewDataDictionary? If Yes, then which one is optimum solution.
Question: When I need to pass values shall I use new {key : value} or use ViewDataDictionary or there is no corelation? Instead of ViewDataDictionary can I use anonymous object in Senario 2
Your two scenarios are totally different. They are not doing the same thing.
In scenario 1 when using this line:
return RedirectToAction("VerifyToken", new { emailId = viewModel.EmailId });
A new URL is genrated and sent back to the client (the browser) with HTTP Code 301 or 302. When received the browser will re-contact your application wiht the received URL. With that URL, your application will execute the associated action. In your case, the client's browser will call VerifyToken action with the emailId parameter setted when you call RedirectionToAction into ForgotPassword action. So using RedirectionToAction method is just telling that method to generate a new URL with parameter defined in the anonymous type.
In scenario 2 is completely different to scenario 1. With this line:
#Html.Partial("../Widget/Customize", Model.Unit, new ViewDataDictionary() { { "ElementName", "UnitWidget" } })
You're telling your view to inject the partial view which path is ../Widget/Customize. Because that partial view the strongly typed, you passed Model.Unit as an second parameter. You use also a third parameter new ViewDataDictionary() { { "ElementName", "UnitWidget" } } because your partial seems to internally need to access to the dynamic property ViewBag or dictionary property ViewData of your view.
Conclusion:
In scenario 1 you are just telling the client's browser to go to the new URL you have generated after requesting ForgetPassword URL. We just call that a rediretion.
In scenario 2, you're just rendering a partial view into a view. The client's broswer doesn't know anything what's going on with partial views they don't know if they exist.
I have a model that I am using in my view that is full of data. This data is then edited in the view. I need to figure out a way to resubmit this data back over to the controller.
Here is what I have so far.
VIEW:
#using (Html.BeginForm("DownloadCSV", "Respondents", FormMethod.Post))
{
#Html.HiddenFor(m => m.FilterSet)
<div class="btn btn-default pull-right" id="dispoCSV" onclick="$('#csvFormSubmit').click()">
<i class="icon-file-alt"></i> Disposition Report
</div>
<input id="csvFormSubmit" type="submit" style="display:none;" />
}
CONTROLLER:
[HttpPost]
public ActionResult DownloadCSV(RespondentsFilterSet model)
{
string csv = "Charlie, Test";
return File(new System.Text.UTF8Encoding().GetBytes(csv), "text/csv", "DispositionReport.csv");
}
MODEL:
public class RespondentsFilterSet : ColdListFilterSet
{
public List<int> OwningRecruiters { get; set; }
public List<int> RecruitingGroups { get; set; }
public override bool HasAtLeastOneFilter()
{
return base.HasAtLeastOneFilter() || OwningRecruiters.IsNotNullOrEmpty() || RecruitingGroups.IsNotNullOrEmpty();
}
public override ExpressionBase ToExpression()
{
var expr = base.ToExpression();
var expressions = expr == null ? new List<ExpressionBase>() : new List<ExpressionBase> { expr };
if (OwningRecruiters.IsNotNullOrEmpty())
{
expressions.Add(new InExpression<int> { Field = Create.Name<Respondent>(r => r.RecruitedBy), Values = OwningRecruiters });
}
if (RecruitingGroups.IsNotNullOrEmpty())
{
expressions.Add(new InExpression<int> { Field = Create.Name<Respondent>(r => r.RecruitingGroupId), Values = RecruitingGroups });
}
return expressions.Count == 0 ? null : BuildAndExpressionFromList(expressions);
}
}
I realize that my controller is not not finalized. I just have displaying some static csv. But I can't figure out why my model from my view is always null when returned to the controller.
Just look at your form. There's not a single input element (except the submit button). You cannot expect to get anything back on the server in this case.
Please read about HTML and how forms work in HTML. In HTML forms you have input fields. Things like text fields, hidden fields, checkboxes, radio buttons, ... - fields that the user interacts with get submitted to the server.
The fact that you have made your HttpPost controller action take some model as parameter doesn't mean at all that this parameter will be initialized. In ASP.NET MVC you have a default model binder. This model binder looks at what gets sent to the server as values when the form is submitted and uses the names of the fields to bind to the corresponding properties. Without input fields in the form, nothing gets sent to the server. Just use the debugging tools built into your web browser to inspect what exactly gets sent to the server.
Contrary to classic ASP.NET WebForms, ASP.NET MVC is stateless. There's no ViewState to remember your model.
So all this rambling is to say that you should read more about HTML forms first and understand the stateless nature of the web before getting into ASP.NET MVC. As far as your particular problem is concerned, well, assuming the user is not supposed to modify any values of the view model in your view throughout some input fields, you could simply include a hidden field containing the id of your model in the form. This id will then be sent to your POST controller action as parameter and you could use it to retrieve your original model from wherever it is stored (I guess a database or something).
I have a view that displays a list of comments. It does this via the DisplayTemplate. All I have to do is something like #Html.DisplayFor(x => x.BlogPost.PostComments) and all the comments render appropriately.
There is a form at the bottom of the page to add a new comment. This page utilizes progressive enhancement. So if javascript is disabled then the form submits like normal, adds the comment to the database, then redirects to the action that renders the blog post. However, if javascript is available then jQuery hijacks the form's submit and makes the post via ajax. Well because the comment markup is in a display template, I don't know how to return it from the action method so that jQuery can drop it on the page.
I know how to do this with partial views. I would just have the action method return the right partial view and jquery would append the response to the comment container on the page.
Before I go chopping out my display template in favor of a partial view, is there a straight forward way that I'm missing to send back a display template from the controller?
Here is my action method:
public ActionResult AddComment(PostComment postComment)
{
postComment.PostedDate = DateTime.Now;
postCommentRepo.AddPostComment(postComment);
postCommentRepo.SaveChanges();
if (Request.IsAjaxRequest())
return ???????
else
return RedirectToAction("BlogPost", new { Id = postComment.BlogPostID });
}
When the page loads it doesn't need to worry about it because it uses the templates in the standard way:
<div class="comments">
#Html.DisplayFor(x => x.BlogPost.BlogPostComments)
</div>
I just want to know how I might send a single comment that utilizes the display template back to jQuery.
You may try returning the partial HTML representing the newly posted comment:
if (Request.IsAjaxRequest())
{
return PartialView(
"~/Views/Shared/DisplayTemplates/Comment.cshtml",
postComment
);
}
and on the client side append this comment to the comments container:
$.post('#Url.Action("AddComment")', { ... }, function (result) {
$('#comments').append(result);
// or $('#comments').prepend(result); if you want it to appear on top
});
Does this question give you what you are looking for? Seems to indicate that you can call a HTML helper from an action.
Create a partial view /Shared/DisplayTemplate.cshtml with the following razor code:
#Html.DisplayFor(m => Model)
Then in your controller (or preferably in a base controller class) add a method along these lines:
protected PartialViewResult PartialViewFor(object model)
{
return PartialView("DisplayTemplate",model);
}
In the OP's case then:
public ActionResult AddComment(PostComment postComment)
{
postComment.PostedDate = DateTime.Now;
postCommentRepo.AddPostComment(postComment);
postCommentRepo.SaveChanges();
if (Request.IsAjaxRequest())
return PartialViewFor(postComment);
else
return RedirectToAction("BlogPost", new { Id = postComment.BlogPostID });
}