Image upload MVC5 - file-upload

I need to upload an image and save it to a file but I can't capture it in the controller as it's always null.
in the view:
#using(Html.BeginForm("Create", "Products", FormMethod.Post, new { enctype = "multipart/form-data "}))
{
....
<input type="file" name="file" id="file" />
Controller:
[HttpPost]
[AcceptVerbs(HttpVerbs.Post)]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ProductID,FKSubCategoryID,ProductEnName,ProductArName,ProductEnDescription,ProductArDescription,ProductImage")] Product product,HttpPostedFileBase file)
{
if (ModelState.IsValid)
{
product.FKBrandID = 1;
db.Products.Add(product);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(product);
}
I have already tried:
Using HttpPostedFileBase instead of HttpPostedFile
Request.Files["file"] is null
Name of the parameter in the input and the ActionResult is the same
enctype = "multipart/form-data ", data_ajax = "false"
Adding [AcceptVerbs(HttpVerbs.Post)]
And this is driving me crazy so please any suggestions?

The problem is, the value you provided for enctype attribute is not valid !
Take a closer look. You have a space before closing the double quotes!
new { enctype = "multipart/form-data "}
It should be new { enctype = "multipart/form-data"}
The below code should work fine.
#using(Html.BeginForm("Create", "Products", FormMethod.Post,
new { enctype = "multipart/form-data"}))
{
<input type="file" name="file" id="file"/>
<input type="submit"/>
}

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.

MVC : Pass values from textbox to controller action

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

MVC Form action method gets overwritten

I have the following razor form:
#model
#using (Html.BeginForm("ResetPassword", "User", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
#Html.Hidden("guid", ViewData["guid"]) ....ect it contains a model and 1 hidden field
When i hit the page i must pass a guid i do this the following way:
User/ResetPassword/8C5F38CC-C8DB-46B4-80F5-169699D8A583
I hit the action controller as expected:
public ActionResult ResetPassword(string id)
{
ViewBag.Title = #DDHelper.GetContent("user_password_reset_new") + " " +
#DDHelper.GetContent("slogan") + " " + #DDHelper.GetMeta("sitename");
if (id != null)
{
Guid pwID = new Guid();
if (Guid.TryParse(id, out pwID))
{
if (UserManager.GetResetPasswordUser(pwID) != null)
{
ViewData["guid"] = id;
return View(new Models.User());
}
}
}
return View();
}
Now when i look at the html razor produced i see:
<form action="/User/ResetPassword/8C5F38CC-C8DB-46B4-80F5-169699D8A583" class="form-horizontal" method="post" role="form">
When i post the form i want to hit the action:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ResetPassword(Models.User pwUser)
{
string guid = Request["guid"];
string password = pwUser.Password;
Guid pwID = new Guid();
if (Guid.TryParse(guid, out pwID))
{
UserManager.ResetUserPassword(password,pwID);
return RedirectToAction("LogOn");
}
return View(guid);
}
Now when I post the form I hit the cshtml again and i am not hitting my action because the action
/User/ResetPassword/8C5F38CC-C8DB-46B4-80F5-169699D8A583
does not exsist and everytime someone hits this page the guid is different.
How can i tell the html.beginform to not write parameters in the action name? and why is razor behaving like this?

Paramete value is null in one action method and non-null in another

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 })) {
....

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();
}
}