Disabling Model Binding for Post Requests - c#

I am trying to implement the file streaming example from https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads
As part of that, I have implemented the filter:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var formValueProviderFactory = context.ValueProviderFactories
.OfType<FormValueProviderFactory>()
.FirstOrDefault();
if (formValueProviderFactory != null)
{
context.ValueProviderFactories.Remove(formValueProviderFactory);
}
var jqueryFormValueProviderFactory = context.ValueProviderFactories
.OfType<JQueryFormValueProviderFactory>()
.FirstOrDefault();
if (jqueryFormValueProviderFactory != null)
{
context.ValueProviderFactories
.Remove(jqueryFormValueProviderFactory);
}
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
I am getting an IOException
System.IO.IOException: Unexpected end of Stream, the content may have
already been read by another component.
at Microsoft.AspNetCore.WebUtilities.MultipartReaderStream.
<ReadAsync>d__36.MoveNext()
This is... vexing. I've found this question
Unexpected end of stream at Microsoft.AspNetCore.WebUtilities.MultipartReaderStream
To which the answer is basically "Implement the DisableFormValueModelBinding Attribute". Obviously, that isn't working.
My Razor code is
<form method="post" enctype="multipart/form-data" asp-controller="FileStore" asp-action="LargeUpload">
<div class="form-group">
<!--<div class="col-md-10">
<p>Please describe your file</p>
<input type="text" name="description"/>
</div>-->
<div class="col-md-10">
<p>Upload one or more files using this form:</p>
<input type="file" name="files" multiple/>
</div>
</div>
<div class="form-group">
<div class="col-md-10">
<input type="submit" value="Large Upload" />
</div>
</div>
</form>
And my Controller is:
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> LargeUpload()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
return BadRequest($"Expected a multipart request, but got {Request.ContentType}");
}
//// Used to accumulate all the form url encoded key value pairs in the
//// request.
//var formAccumulator = new KeyValueAccumulator();
//string targetFilePath = null;
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
}
Does anyone know if I've missed something or if the tutorials are out of date?
I'm running VS2017 community with ASP.NET Core 1.1.2

Related

Redirect back to Previous Page without losing original data

I have trouble preserving the original data by redirecting the same page when my custom error handling is executed in the controller.
Assume that I have a web page call Create.cshtml.
In that create webpage, I have a few form control which require the user to enter class code but the class code cannot be duplicated.
Assume that the user entered a class code that is existed in the system, my system should redirect back to Create.cshtml and pass error message (E.g. ViewBag.error = "Class Code duplicated") and simulatenously .
But my current implementation does not revert back the original content/data after redirect.
ClassController:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("ID,ClassCode,ClassName,DateCreation,DegreeID,CourseChapterID")] Class #class)
{
if (ModelState.IsValid)
{
Class cls = await _context.Class.SingleOrDefaultAsync(c => c.ClassCode == #class.ClassCode);
if (cls != null)
{
TempData["error"] = "This class code has been existed in the system";
ModelState.AddModelError("error", "This class code has been existed in the system");
return RedirectToAction(nameof(Create),#class);
}
_context.Add(#class);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(#class);
}
Create.cshtml
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="ClassCode" class="control-label"></label>
<input asp-for="ClassCode" class="form-control" />
<span asp-validation-for="ClassCode" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ClassName" class="control-label"></label>
<input asp-for="ClassName" class="form-control" />
<span asp-validation-for="ClassName" class="text-danger"></span>
</div>
#if (#TempData["error"] != null)
{
<div class="form-group">
<label class="control-label">#TempData["error"]</label>
</div>
}
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
System environment: .NET Core Entity Framework
When you use redirection, you should use TempData instead of ViewBag.
TempData["error"] = "This class code has been existed in the system";
return RedirectToAction(nameof(Create));
You are redirect to the create get method, if it doesn't return the model, the data will lose, I think you can directly return View() here.
if (cls != null)
{
TempData["error"] = "This class code has been existed in the system";
ModelState.AddModelError("error", "This class code has been existed in the system");
return View(nameof(Create), #class);
}

File input sending null to controller [duplicate]

This question already has answers here:
MVC HttpPostedFileBase always null
(2 answers)
Closed 2 years ago.
I have a file input inside of a form. Ideally I'd like it to allow uploading multiple files on form submit. It was sending empty list so I tried changing to just receive a single file, now it's sending null.
My cshtml form:
#using (Html.BeginForm(nameof(ServiceCallController.AddMessage), "ServiceCall", FormMethod.Post))
{
#Html.HiddenFor(m => m.Id)
<div class="row">
<div class="col-md-1">
#Html.Label("New Message")
</div>
<div class="col-md-11">
#Html.Kendo().EditorFor(m => m.NewMessage).Tools(t => t.Clear())
</div>
</div>
<div class="row">
<div class="col-md-1">
#Html.Label("Attachments")
</div>
<div class="col-md-11">
<input type="file"
id="myFile"
name="myFile"
/>
</div>
</div>
<div class="row">
<div class="col-md-12">
#Html.Kendo().Button().Content("Add Message").Name("SubmitAddMessage")
</div>
</div>
}
My controller method:
public ActionResult AddMessage(int id, string newMessage, HttpPostedFileBase myFile)
{
if (myFile == null) throw new ArgumentNullException(nameof(myFile));//This should not happen
return RedirectToAction(nameof(MyView), new { id });
}
And the contents being sent in the network tab request form data:
Id: 5473
NewMessage: Why is this not working?
myFile: Chrysanthemum.jpg
What am I doing wrong? Why is my file not being sent to the controller?
You should add enctype as multipart/form-data
#using (Html.BeginForm(nameof(ServiceCallController.AddMessage), "ServiceCall", FormMethod.Post, new { enctype = "multipart/form-data" }))
And the action method:
[HttpPost]
public ActionResult AddMessage(int id, string newMessage, HttpPostedFileBase myFile)
{
if (myFile == null) throw new ArgumentNullException(nameof(myFile));//This should not happen
return RedirectToAction(nameof(MyView), new { id });
}

Entity edit doesn't save in ASP.NET MVC Core

I got a form for editing my user info:
<form asp-action="EditUser" asp-route-returnurl="#ViewData["ReturnUrl"]">
<div class="form-horizontal">
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="User.Id" />
<div class="form-group">
<label asp-for="User.Name" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="User.Name" class="form-control" />
<span asp-validation-for="User.Name" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="User.Surname" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="User.Surname" class="form-control" />
<span asp-validation-for="User.Surname" class="text-danger"></span>
</div>
</div>
</div>
</form>
Here is my MVC action:
[HttpPost, ActionName("EditUser")]
[Authorize(Roles = Roles.Root + "," + Roles.Manager)]
public async Task<IActionResult> Edit(string id, string returnUrl = null)
{
if (ModelState.IsValid)
{
try
{
var studentToUpdate = await _dbContext.Users.SingleOrDefaultAsync(s => s.Id == id);
// Try to update user from DB by edited values from form
if (await TryUpdateModelAsync<ApplicationUser>(studentToUpdate, "User.",
s => s.Surname, u => u.Name))
_dbContext.SaveChanges();
return Redirect(returnUrl);
}
}
}
In debug I could see that in Request.Form property there are new right values, but DB entity hasnt updated. How could I fix that?
We normally use a view model as a parameter, and bind the posted value to it. Then update the record base on it.
[HttpPost, ActionName("EditUser")]
[Authorize(Roles = Roles.Root + "," + Roles.Manager)]
public async Task<IActionResult> Edit(string id, EditViewModel model,
string returnUrl = null)
{
if (ModelState.IsValid)
{
var studentToUpdate = await _dbContext.Users.SingleOrDefaultAsync(s => s.Id == id);
if (studentToUpdate != null)
{
studentToUpdate.Surname = model.Surname;
... Other properties
await _dbContext.SaveChangesAsync();
}
return Redirect(returnUrl);
}
return View(model);
}

How to email Modal

I'm fairly new to asp .net MVC, I've got a modal wish accepts a username, email address, comments, and also has a submit button. I'm looking create the submit functionality such that when it's pressed it'll send an email.
I've had passed experience sending emails in c# no problem, the troubles I'm having is linking the two together.
<div class="modal fade" id="contact" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<form class="form-horizontal">
<div class="modal-header">
<h4>Contact Tech Site</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="contact-name" class="col-lg-2 control-label">Name:</label>
<div class="col-lg-10">
<input type="text" class="form-control" id="contact-name" placeholder="Full Name">
</div>
</div>
<div class="form-group">
<label for="contact-email" class="col-lg-2 control-label">Email:</label>
<div class="col-lg-10">
<input type="email" class="form-control" id="contact-email" placeholder="you#example.com">
</div>
</div>
<div class="form-group">
<label for="contact-msg" class="col-lg-2 control-label">Message:</label>
<div class="col-lg-10">
<textarea class="form-control" rows="8"></textarea>
</div>
</div>
</div>
<div class="modal-footer">
<a class="btn btn-default" data-dismiss="modal">Close</a>
<button class="btn btn-primary" type="submit">Send</button>
</div>
</form>
</div>
</div>
</div>
Email Code
var SMTP_USERNAME = User.EmailUsername;
var SMTP_PASSWORD = EncryptionHelper.Decrypt(User.EmailPassword);
Mail.Subject = Subject;
Mail.Body = EmailText;
foreach (var to in SelectedUsers)
{
foreach (var contactMethod in to.ContactMethods.Where(x => x.Primary && x.Channel == ContactMethod.Channels.Email))
{
Mail.To.Add(contactMethod.Value);
}
}
Mail.From = new MailAddress(SMTP_USERNAME, User.FullName());
//Server
var HOST = unitOfWork.SettingRepository.GetString(KnownKeys.SMTPServer);
//Port
var PORT = int.Parse(unitOfWork.SettingRepository.GetString(KnownKeys.SMTPPort));
// Create an SMTP client with the specified host name and port.
var emailSent = false;
using (SmtpClient client = new SmtpClient(HOST, PORT))
{
// Create a network credential with your SMTP user name and password.
client.Credentials = new System.Net.NetworkCredential(SMTP_USERNAME, SMTP_PASSWORD);
// Use SSL when accessing Amazon SES. The SMTP session will begin on an unencrypted connection, and then
// the client will issue a STARTTLS command to upgrade to an encrypted connection using SSL.
client.EnableSsl = true;
// Send the email.
try
{
client.Send(Mail);
emailSent = true;
}
catch (Exception ex)
{
MessageBox.Show("Error message: " + ex.Message);
}
}
Create a model -
public class EmailViewModel
{
public string Username { get; set; }
public string Email { get; set; }
public string Comments { get; set; }
}
And then create your controller -
public class HomeController : Controller
{
public ActionResult GetEmailForm()
{
return View();
}
public ActionResult SubmitEmail(EmailViewModel model)
{
var result = SendEamil(model);
return View();
}
private bool SendEamil(EmailViewModel model)
{
// Use model and send email with your code.
return true;
}
}
Basically GetEmailForm action will return you a view with form -
#model MvcApplication1.Controllers.EmailViewModel
#{
ViewBag.Title = "GetEmailForm";
}
<h2>GetEmailForm</h2>
<link href="../../Content/bootstrap.css" rel="stylesheet" />
#using (Html.BeginForm("SubmitEmail", "Home", FormMethod.Post))
{
<div id="contact">
<div class="modal-dialog">
<div class="modal-content">
<form class="form-horizontal">
<div class="modal-header">
<h4>Contact Tech Site</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="contact-name" class="col-lg-2 control-label">Name:</label>
<div class="col-lg-10">
#Html.TextBoxFor(m => m.Username, new { #placeholder = "Full Name"})
</div>
</div>
<div class="form-group">
<label for="contact-email" class="col-lg-2 control-label">Email:</label>
<div class="col-lg-10">
#Html.TextBoxFor(m => m.Email, new { #placeholder = "you#example.com"})
</div>
</div>
<div class="form-group">
<label for="contact-msg" class="col-lg-2 control-label">Message:</label>
<div class="col-lg-10">
#Html.TextAreaFor(m => m.Comments, new { #placeholder = "Comments"})
</div>
</div>
</div>
<div class="modal-footer">
<a class="btn btn-default" data-dismiss="modal">Close</a>
<button class="btn btn-primary" type="submit">Send</button>
</div>
</form>
</div>
</div>
</div>
}
When you enter data and click on submit, it will post the viewmodel with data to SubmitEmail action of same controller.
Output of the page is as shown below, sorry for styles, i have no time so removed some of the styles.
When you enter data and click on submit, you get data as shown below -
Once you have the data, you can use that in your private method SendEmail (which will have your code) to send email.
Check the MSDN site for information on how to create a form, it will get submitted when the user presses the submit button. Back in yoru controller add a second version of your method with the HttpPost attribute and give it a parameter instance of your model, MVC will take care of the mapping for you. See BUILDING ASP.NET MVC FORMS WITH RAZOR for more details.
Call a action method From your Email Controller thru a ajax call.From the below Code Snippet you will get an idea how to do it.
$('#btnSubmit').on('click', function () { //Ur Button
$.ajax({
type: "POST",
url: "/EmailController/SendEmailAction",
data: "{Email:" + $("#contact-email").val() +"}", // Reading from extboxes and Converting to JSON for Post to EmailController Action
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(result) {
//alert message
//Close the Modal
}
});
});
And In C# level in youecontroller
public JsonResult _SendEmail(EmailViewModel model)
{
//Here your code to send Email
}
I hope now you will get an idea how to go about it.
I think you do not require a form and submit button to send an email from a bootstrap modal.

Submit to a new page and download a file

I have code that allows users to create and edit records in a SQL database. This works fine. However, now I want it to output the SQL scripts for what is being done. I'm not sure how to do this. Here is my current code:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true)
<div class="form-group">
<strong>ID:</strong>
<div class="col-md-10">
<p>
#Html.EditorFor(model => model.ID)
#Html.ValidationMessageFor(model => model.ID)
</div>
</div>
<br />
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-primary">Submit <i class="fa fa-caret-right"></i></button>
</div>
</div>
</div>
}
public ActionResult Edit(int id)
{
using (var client = new XServiceClient())
{
X x = client.Find(id);
if (x == null)
{
return HttpNotFound();
}
return View(x);
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(X x)
{
if (ModelState.IsValid)
{
using (var client = new XServiceClient())
{
client.Edit(x);
return Redirect("~/Home/Index");
}
}
return View(x);
}
I'm thinking in my controller I could do something like this:
using (var client = new XServiceClient())
{
var sessionID = Guid.NewGuid().ToString("N");
var filename = String.Format("Session-{0}.sql", sessionID);
var sqlString = "Update SQL String Stuff";
File(System.Text.UTF8Encoding.UTF8.GetBytes(sqlString), "text/plain", filename);
client.Edit(x);
return Redirect("~/Home/Index");
}
Most systems I have seen recommend a return on that File line. However, that won't work with the rest of the program.
How can I get my submit button to submit the data to the server and also download a file to the user?

Categories