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();
}
}
Related
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.
I have what I hope is a simple question. I am trying to use multiple forms on an MVC view (like at Multiple Forms in same page ASP.net MVC) - each calling different actions on the same controller. My issue is that any form where I call an action that is named different from the view I get an error. Simple example below:
Index.cshtml
#using (Html.BeginForm("index", null, FormMethod.Post, new { #class = "form -horizontal" }))
{
<button class="btn btn-primary" type="submit">index</button>
}
<br />
#using (Html.BeginForm("foobar", null, FormMethod.Post, new { #class = "form -horizontal" }))
{
<button class="btn btn-primary" type="submit">foobar</button>
}
testController
public class testController : Controller
{
// GET: test
public ActionResult Index()
{
return View();
}
[HttpPost, ActionName("Index")]
public ActionResult IndexPost()
{
return View();
}
[HttpPost]
public ActionResult foobar()
{
return View();
}
Clicking the "index" button works (does nothing)
Clicking the "foobar" button throws an error
The view 'foobar' or its master was not found or no view engine supports the searched locations.
I am obviously missing something - any wisdom is appreciated
I hope this would help:
[HttpPost, ActionName("Index")]
public ActionResult IndexPost()
{
return View("Index");
}
[HttpPost]
public ActionResult foobar()
{
return View("Index");
}
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
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);
}
}
I have controller called Documents with three action methods:
public ActionResult Save(string returnUrl){
TempData["returnUrl"]=returnUrl;
return View(viewName: "Save");
}
[HttpPost]
public ActionResult Save(string returnUrl){
return Redirect(returnUrl);
}
and
[HttpPost]
public ActionResult Cancel(string returnUrl){
return Redirect(returnUrl);
}
And here's the content of the Save.cshtml view:
#Html.Hidden(TempData["returnUrl"].ToString())
#using (Html.BeginForm){
<!--Some html here-->
<input type="submit" value="Save"/>
}
#using (Html.BeginForm(actionName:"Cancel",controllerName:"Documents")){
<input type="submit" value="Cancel"/>
}
Of course, the above code does not reflect what I need to do in real world but one problem made me strip my code down to this simplest stage. The problem is that the returnUrl argument is null when I call the Cancel action method. Why is that?
In order to post back to FormCollection, inputs associated with the form need to be located inside the <form> tag (except if using the form attribute). In your case where you have 2 forms and need to post back the value of returnUrl, you would need 2 inputs. This will create elements with duplicate id's if using html helpers. A better approach would be to include the value in the form element, for example
Controller
[HttpGet]
public ActionResult Save(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View("Save");
}
[HttpPost]
public ActionResult Save(string returnUrl, MyModel, model)
{
....
}
[HttpPost]
public ActionResult Cancel(string returnUrl, MyModel, model)
{
....
}
View
#using (Html.BeginForm("Save", "Documents", new { ReturnUrl = ViewBag.ReturnUrl })) {
....
#using (Html.BeginForm("Cancel", "Documents", new { ReturnUrl = ViewBag.ReturnUrl })) {
....