MVC : Pass values from textbox to controller action - asp.net-mvc-4

I am new to MVC.Basically I need to pass values entered in the textbox from my view to controller action method. As I enter the values in the text box and click the enter button I need to display the value on the screen. I am currently unable to do so. Please find my code below
The model class
public class ProteinTrackingService
{
public int? Total { get; set; }
public int Goal { get; set; }
public void AddProtein(int? amount)
{
Total += amount;
}
}
The controller class
public class ProteinTrackerController : Controller
{
ProteinTrackingService proteinTrackingService = new ProteinTrackingService();
// GET: ProteinTracker
public ActionResult Index()
{
ViewBag.Total = proteinTrackingService.Total;
ViewBag.Goal = proteinTrackingService.Goal;
return View();
}
// GET: ProteinTracker/Details/5
public ActionResult AddProtein(ProteinTrackingService model)
{
proteinTrackingService.AddProtein(model.Total);
ViewBag.Total = proteinTrackingService.Total;
ViewBag.Goal = proteinTrackingService.Goal;
return View("Index");
}
}
The view
using (Html.BeginForm("ProteinTracker", "AddProtein",FormMethod.Post))
{
#Html.AntiForgeryToken()
<form>
<div class="form-horizontal">
<h4>Protein Tracker</h4>
<hr />
Total : #ViewBag.Total
Goal : #ViewBag.Goal
<input id="Text1" type="text" value="TextInput" /> <input type="Submit" value="Add" />
</div>
</form>
}
I am modifying the code above based on your suggestions. I basically need to display the following in the view
Total : value
Goal : value
Textbox control (To enter the total) Button (pass the total to contoller) Please note that when the user clicks the Add button the total should show in above field Total : value.
New View
#using (Html.BeginForm( "AddProtein","ProteinTracker", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Protein Tracker</h4>
<hr />
#Html.LabelFor(m => m.Total, "Total" ) <hr />
#Html.LabelFor(m => m.Goal, "Goal") <hr />
#Html.TextBoxFor(m => m.Total) <hr />
<input type="Submit" value="Add" />
</div>
}
New Controller
public class ProteinTrackerController : Controller
{
ProteinTrackingService proteinTrackingService = new ProteinTrackingService();
// GET: ProteinTracker
public ActionResult Index()
{
var model = new ProteinTrackingService()
{ Total = proteinTrackingService.Total, Goal = proteinTrackingService.Goal };
return View(model);
}
// GET: ProteinTracker/Details/5
public ActionResult AddProtein(ProteinTrackingService model)
{
proteinTrackingService.AddProtein(model.Total);
model.Total = proteinTrackingService.Total;
model.Goal = proteinTrackingService.Goal;
return View("Index",model);
}
}

You need to add the HttpPost attribute to your action.Looking at your form #using (Html.BeginForm( "AddProtein","ProteinTracker", FormMethod.Post)) , apparently you are sending a post request to your controller.
[HttpPost]
public ActionResult AddProtein(ProteinTrackingService model)
{
proteinTrackingService.AddProtein(model.Total);
model.Total = proteinTrackingService.Total;
model.Goal = proteinTrackingService.Goal;
return View("Index",model);
}

First of all your this syntax
using (Html.BeginForm("ProteinTracker", "AddProtein", FormMethod.Post))
already creates a form tag when html generates. No need to create from tag again in it.
So for your want, in view you need give to your input field a name
<input id="Text1" type="text" value="TextInput" name="textname"/>
and add this name as parameter in your controller method like that
public ActionResult AddProtein(ProteinTrackingService model,string textname)
{
// your code
return View("Index");
}
It will pass your textfield value from view to controller. For clearing your concept you may visit Loopcoder.com

Related

IValidationAttributeAdapterProvider is called only for EmailAddressAttribute

What I was doing with ASP.NET MVC 5
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MaxLengthAttribute), typeof(MyMaxLengthAttributeAdapter));
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredAttribute), typeof(MyRequiredAttributeAdapter));
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MinLengthAttribute), typeof(MyMinLengthAttribute));
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(EmailAddressAttribute), typeof(MyEmailAddressAttributeAdapter));
Now I'm migrating it to ASP.NET core 6
We can't use DataAnnotationsModelValidatorProvider anymore so I'm trying to use IValidationAttributeAdapterProvider, which doesn't work properly for me.
My codes
My IValidationAttributeAdapterProvider is below.
public class MyValidationAttributeAdapterProvider : ValidationAttributeAdapterProvider, IValidationAttributeAdapterProvider
{
IAttributeAdapter? IValidationAttributeAdapterProvider.GetAttributeAdapter(
ValidationAttribute attribute,
IStringLocalizer? stringLocalizer)
{
return attribute switch
{
EmailAddressAttribute => new MyEmailAddressAttributeAdapter((EmailAddressAttribute)attribute, stringLocalizer),
MaxLengthAttribute => new MyMaxLengthAttributeAdapter((MaxLengthAttribute)attribute, stringLocalizer),
MinLengthAttribute => new MyMinLengthAttribute((MinLengthAttribute)attribute, stringLocalizer),
RequiredAttribute => new MyRequiredAttributeAdapter((RequiredAttribute)attribute, stringLocalizer),
_ => base.GetAttributeAdapter(attribute, stringLocalizer),
};
}
}
My model class is below.
public class LogInRequestDTO
{
[Required]
[EmailAddress]
[MaxLength(FieldLengths.Max.User.Mail)]
[Display(Name = "mail")]
public string? Mail { get; set; }
[Required]
[MinLengthAttribute(FieldLengths.Min.User.Password)]
[DataType(DataType.Password)]
[Display(Name = "password")]
public string? Password { get; set; }
}
And in my Program.cs, I do like below.
builder.Services.AddControllersWithViews()
.AddDataAnnotationsLocalization(options =>
{
options.DataAnnotationLocalizerProvider = (type, factory) => factory.Create(typeof(Resources));
});
builder.Services.AddSingleton<IValidationAttributeAdapterProvider, MyValidationAttributeAdapterProvider>();
What happed to me
I expect GetAttributeAdapter is called for each attribute like EmailAddressAttribute, MaxLengthAttribute, etc.
But it's called only once with EmailAddressAttribute.
So, all other validation results are not customized by my adaptors.
If I remove [EmailAddress] from the model class, GetAttributeAdapter is never called.
Am I missing something?
Added on 2022/05/24
What I want to do
I want to customize all the validation error message.
I don't want to customize for one by one at the place I use [EmailAddress] for example.
I need the server side validation only. I don't need the client side validation.
Reproducible project
I created the minimum sample project which can reproduce the problem.
https://github.com/KuniyoshiKamimura/IValidationAttributeAdapterProviderSample
Open the solution with Visual Studio 2022(17.2.1).
Set the breakpoint on MyValidationAttributeAdapterProvider.
Run the project.
Input something to the textbox on the browser and submit it.
The breakpoint hits only once with EmailAddressAttribute attribute.
The browser shows the customized message for email and default message for all other validations.
Below is a work demo, you can refer to it.
In all AttributeAdapter, change your code like below.
public class MyEmailAddressAttributeAdapter : AttributeAdapterBase<EmailAddressAttribute>
{
// This is called as expected.
public MyEmailAddressAttributeAdapter(EmailAddressAttribute attribute, IStringLocalizer? stringLocalizer)
: base(attribute, stringLocalizer)
{
//attribute.ErrorMessageResourceType = typeof(Resources);
//attribute.ErrorMessageResourceName = "ValidationMessageForEmailAddress";
//attribute.ErrorMessage = null;
}
public override void AddValidation(ClientModelValidationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-must-be-true", GetErrorMessage(context));
}
// This is called as expected.
// And I can see the message "Input the valid mail address.".
public override string GetErrorMessage(ModelValidationContextBase validationContext)
{
return GetErrorMessage(validationContext.ModelMetadata, validationContext.ModelMetadata.GetDisplayName());
}
}
In homecontroller:
public IActionResult Index()
{
return View();
}
[HttpPost]
public IActionResult Index([FromForm][Bind("Test")] SampleDTO dto)
{
return View();
}
Index view:
#model IV2.Models.SampleDTO
#{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<h4>SampleDTO</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Index">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Test" class="control-label"></label>
<input asp-for="Test" class="form-control" />
<span asp-validation-for="Test" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Result1:
Result2:
I found the solution.
What I have to use is not ValidationAttributeAdapterProvider but IValidationMetadataProvider.
This article describes the usage in detail.
Note that some attributes including EmailAddressAttribute have to be treated in special way as describe here because they have default non-null ErrorMessage.
I confirmed for EmailAddressAttribute and some other attributes.
Also, there's the related article here.

How can I link a DropDown and Textbox to my Model's data?

Here is the code for my Model. ListBuilder.DropDown is part of a common class of functions, which simply returns a List when provided the string name of a stored procedure that will be called on the database.
There is some more shared common class code (stored procedure related) with in the try statement, but that implementation is irrelevant to the problem I'm having. The data is successfully retrieved and stored into the model.
public class PositionViewModel
{
[Display(Name = "Series")]
public string series { get; set; }
public int seriesID { get; set; }
public List<SelectListItem> list_Series { get; set; }
}
public PositionViewModel(string id)
{
Get(id);
this.list_Series = ListBuilder.DropDown(AppConstants.StoredProc_GetSeries);
}
public Position Get(string id)
{
ExecStoredProcedure sp = null;
DataTable dt = new DataTable();
try
{
sp = new ExecStoredProcedure(AppConstants.SP_POSITION_GET, new ConnectionManager().GetConnection(), AppConstants.SP_POSITION_GET);
sp.SPAddParm("#PD_ID", SqlDbType.Char, id, ParameterDirection.Input);
dt = sp.SPselect();
if (dt.Rows.Count > 0)
{
this.pd_id = dt.Rows[0]["PD_ID"].ToString();
this.official_title = dt.Rows[0]["label"].ToString();
this.series = dt.Rows[0]["Series"].ToString();
this.grade = dt.Rows[0]["Grade"].ToString();
this.PDType = dt.Rows[0]["PDType"].ToString();
}
}
catch (Exception ex)
{
throw ex;
}
finally
{
sp.dbConnection.Close();
}
return this;
}
Here is the code for my Controller
[HttpGet]
public ActionResult PositionEdit(string id)
{
PositionViewModel model = new PositionViewModel(id);
return View("PositionEdit", model);
}
[HttpPost]
public ActionResult PositionEdit(PositionViewModel model)
{
if (ModelState.IsValid)
{
int rc = model.Update();
return RedirectToAction("PositionView");
}
else
{
return View("PositionEdit", model);
}
}
Here is the code for my view. What I'd like to have is a dropdownlist that contains the model.seriesID (a sequence number) but as the user selects an item, it will update the textbox with model.series (the name of the series)
#model Project.Models.PositionViewModel
#{
ViewBag.Title = "Edit Position Description";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="editor-label">
#Html.LabelFor(model => model.series)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => Model.seriesID, Model.list_Series, new { style = "width:550px" })
#Html.ValidationMessageFor(model => Model.seriesID)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.series, new { style = "width:250px;" })
#Html.ValidationMessageFor(model => model.series)
</div>
<div class="toppad20">
<input type="submit" value="Save" />
</div>
}
I am having trouble linking the dropdownlist with the textbox. Do I need some kind of onChange event? Thanks in advance for your help.
Your solution involves passing a string into your view model's constructor. However, on post, the model binder will be incapable of instantiating your view model with anything but the parameterless constructor. That's part of the reason, but not the only reason, that view models should not handle things like datastore access. That is the job of the controller.
On your view model, leave your list property as a auto-implemented property and then in your controller call ListBuilder.DropDown, which you can use data from your model to call, at that point.

Refer to hidden type ID in controller page

I need to use hdNoOfColumns and hdNoOfRows in a controller page which are defined as hidden type in View page. I am getting a "context does not exist" error. How do refer to a hidden type ID in the controller page?
View:
<input id="hdNoOfRows" type="hidden" name="hdNoOfRows" />
<input id="hdNoOfColumns" type="hidden" name="hdNoOfColumns"/>
Controller:
hdNoOfColumns.Value = count.ToString();
dsCount = ds.Tables[0].Rows.Count;
hdNoOfRows.Value = dsCount.ToString();
seatCount = dsS.Tables[0].Rows.Count;
asp.net mvc does not work as asp.net web forms, you need to create a model and then use it:
First Create a Model:
public class TableModel
{
public string NoOfColumns { get; set;}
public string NoOfRows { get;set; }
}
Now in your controller action:
public class BarController : Controller
{
public ActionResult Foo()
{
TableModel model = new TableModel();
model.NoOfColumns= count.ToString();
dsCount = ds.Tables[0].Rows.Count;
model.NoOfRows= dsCount.ToString();
seatCount = dsS.Tables[0].Rows.Count;
return View(model);
}
}
Now in your View use HiddenFor() :
#model TableModel
#Html.HiddenFor(x=>x.NoOfColumns)
#Html.HiddenFor(x=>x.NoOfRows)
post them by putting them inside form :
and For posting back values to controller you will have to put them in a form:
#model TableModel
#using(Html.BeginForm("Foo", "Bar", FormMethod.Post))
{
<input id="hdNoOfRows" type="hidden" name="hdNoOfRows" />
<input id="hdNoOfColumns" type="hidden" name="hdNoOfColumns"/>
<input type="submit" value="Post"/>
}
and then in your controller controller handle in post action of it:
public class BarController : Controller
{
public ActionResult Foo()
{
TableModel model = new TableModel();
model.NoOfColumns= count.ToString();
dsCount = ds.Tables[0].Rows.Count;
model.NoOfRows= dsCount.ToString();
seatCount = dsS.Tables[0].Rows.Count;
return View(model);
}
[HttpPost]
public ActionResult Foo(TableModel model)
{
// do saving in db or whatever business logic
return View(model);
}
}

Why submit button does't call controller function?

I have a small problem with calling controller function. Strange is that every other submit button works fine. But this one has problem which I cannot solve for now.
I will show you two forms with submit buttons becouse there is only one working fine.
Controller:
public class MyController : Controller
{
public ActionResult MethodOne()
{
...
return RedirectToAction("index");
}
public ActionResult MethodTwo()
{
...
return RedirectToAction("index");
}
}
And the view:
//This one works fine!!
#using (Html.BeginForm("MethodOne", "My", FormMethod.Post))
{
<input id="Some-cool-id" type="submit" value="Add!" />
}
//This one doesn't work?!
#using (Html.BeginForm("MethodTwo", "My", FormMethod.Post))
{
<input id="some-cool-id2" type="submit" value="Delete"!" />
}
Error is telling that Method2 is not in the required path.
Resource not found.
Description: HTTP 404. Searched resource (or ...) ...
Required path URL: /My/MethodTwo
I was searching what is bad but in the end, I need a help, thanks.
Add the property [HttpPost] before the method.
Try this,
#using (Html.BeginForm("MethodTwo", "Test", FormMethod.Post))
{
<input type="submit" value="asd" />
}
#using (Html.BeginForm("MethodOne", "Test", FormMethod.Post))
{
<input type="submit" value="poll" />
}
Controller
public class TestController : Controller
{
public ActionResult MethodOne()
{
return RedirectToAction("CustomerInfo");
}
public ActionResult MethodTwo()
{
return RedirectToAction("CustomerInfo");
}
[HttpGet]
public ActionResult CustomerInfo()
{
ViewBag.CustomerNameID = new SelectList(List, "CustomerId", "customerName");
ViewBag.RegisterItems = GetAllRegisterData();
ViewData["SampleList"] = GetAllRegisterData();
return View();
}
}

Same url but different controllers?

Is it possible to use two different controllers for the same url?
This is needed because I need the URL to always remain the same, but it should use different controllers. My controllers (Apples, Bananas, etc.) and views are separated into each own project.
I need a action in my main MVC project to return a action/view from either the Bananas or Apples project depending on some logic.
So how would I go ahead to always have the same url but return actions/views from different controllers?
I'm using MVC 4
Your URLs should be where the logic for selecting your controller is. Maybe you need to reorganise your project to have a single controller and put the other logic in the controller action for filling the model?
However, if you insist on going this route you will likely need to override CreateController in the DefaultControllerFactory, this is the class that instantiates your controller, usually based on your controller name. Here is an example in one of my projects:
public class ErrorHandlingControllerFactory : DefaultControllerFactory
{
/// <summary>
/// Injects a custom attribute
/// on every action that is invoked by the controller
/// </summary>
/// <param name="requestContext">The request context</param>
/// <param name="controllerName">The name of the controller</param>
/// <returns>An instance of a controller</returns>
public override IController CreateController(
RequestContext requestContext,
string controllerName)
{
var controller =
base.CreateController(requestContext,
controllerName);
var c = controller as Controller;
if (c != null)
{
c.ActionInvoker =
new ErrorHandlingActionInvoker(
new HandleErrorWithELMAHAttribute());
}
return controller;
}
}
You will need to set your route up to pass a known controller name (horrible magic strings...), test for this controller name, and if detected run your logic to get the actual controller name and pass this in to base.CreateController.
I wrote these codes. I hope that it helps you. I used hidden field to understand which method will run.
these are my models:
namespace MvcSameController.Models
{
public class RouteModel
{
public SampleModel1 SampleModel1 { get; set; }
public SampleModel2 SampleModel2 { get; set; }
}
public class SampleModel1
{
public int Id { get; set; }
public string Name { get; set; }
}
public class SampleModel2
{
public int Id { get; set; }
public string Surname { get; set; }
}
}
this is controller:
using System.Web.Mvc;
using MvcSameController.Models;
namespace MvcSameController.Controllers
{
public class SameController : Controller
{
//
// GET: /Same/
public ActionResult Index()
{
return View();
}
[HttpPost]
public void Index(RouteModel routeModel, string type)
{
if (type == "1")
{
//Code for type 1
}
else if (type == "2")
{
//Code for type 2
}
}
}
}
and view :
#{
ViewBag.Title = "Index";
}
#model MvcSameController.Models.RouteModel
<section id="loginForm">
<h2>Type1 </h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.Hidden("type",1)
<fieldset>
<legend>Type1 Form</legend>
<ol>
<li>
#Html.LabelFor(m => m.SampleModel1.Name)
#Html.TextBoxFor(m => m.SampleModel1.Name)
#Html.ValidationMessageFor(m => m.SampleModel1.Name)
</li>
</ol>
<input type="submit" value="Run Method1" />
</fieldset>
}
</section>
<section id="loginForm">
<h2>Type2</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.Hidden("type",2)
<fieldset>
<legend>Type2 Form</legend>
<ol>
<li>
#Html.LabelFor(m => m.SampleModel2.Surname)
#Html.TextBoxFor(m => m.SampleModel2.Surname)
#Html.ValidationMessageFor(m => m.SampleModel2.Surname)
</li>
</ol>
<input type="submit" value="Run Method2" />
</fieldset>
}
</section>
you can download my sample from here