Post Request is not returning same object as passed - asp.net-mvc

I am trying to do a simple CRUD app asp.net core using MVC and the strangest thing is happening to me.
create the model and pass it in to the form but when I go to save it, it no longer has the Id that I passed into it.
[HttpGet]
public IActionResult CreateCompany(Guid id)
{
//id = 677b57f1-d0b2-484b-9892-b06e6eb9f1f7
var pageId = id;
var company = new CompanyListItem() {PublicPageId = pageId};
return View(company);
}
[HttpPost]
public IActionResult CreateCompany(CompanyListItem model)
{
//model.PublicPageId = 00000000-0000-0000-0000-000000000000
if (ModelState.IsValid)
{
model.Id = Guid.NewGuid();
var newModel = _companyDataProvider.Add(model);
PublicPageViewModel page = null;
if (newModel != null)
{
page = _pageDataProvider.GetPageIdFromCompanyListId(newModel.Id);
}
if (page != null)
{
return RedirectToAction("Details", page);
}
}
return View();
}
my form:
#model CompanyListItem
<h1>Add Company</h1>
<form method="post">
<div class="col-md-6">
<div class="form-group">
<label asp-for="Header"></label>
<input asp-for="Header" class="form-control"/>
</div>
<div>
<input type="submit" class="btn btn-success" value="save"/>
<a class="btn btn-default">Cancel</a>
</div>
</div>
</form>
Does anyone know why this is happening? Or how i can prevent it from happening?

Related

Updating View with changed ViewModel after Form Post

I'm trying to update an MVC view to display a message after a form has been posted but cannot get it working. I'm new to this so would appreciate a few pointers on what I'm doing wrong. This isn't form validation as the logic and response message won't be known until after the form has been posted and processed.
The controller action [HttpPost] public async Task<IActionResult> PlatformRecord(RecAction RecActionNotUSed) fires on the Form POST and does some codebehind logic - I then need to update the View with a Response which is only a temporary message and not stored anywhere.
The initial GET request works fine and does display the message but just cannot figure out how to do the same after a Form POST.
I've tried adding ModelState.Clear(); without success. Also, if I redirect to the initial view I lose the Response message eg return RedirectToAction("PlatformRecord"); means I no longer have RecAction.Response = "NEED TO SHOW THIS RESPOSNE MESSAGE AFTER FORM POST.";
My code is as follows
VIEWMODEL:
public class RecAction
{
public RecUser RecUser { get; set; }
public string Response { get; set; }
}
CONTROLLER:
public class RecordManagerController : Controller
{
private readonly IOptions<ConnectionStrings> _connectionStrings;
private readonly UserManager<AppRecationUser> _userManager;
Public RecordManagerController(UserManager <AppRecationUser> UserManager,
IOptions <connectionStrings> connectionStrings)
{
_userManager = userManager;
_connectionStrings = connectionStrings;
}
// GET: /<controller>/
public IActionResult Index()
{
return View();
}
private Task<AppRecationUser> GetCurrentUserAsync()
{
return _userManager.GetUserAsync(HttpContext.User);
}
public async Task<IActionResult> PlatformRecord()
{
var RecordDataModel = new RecordDataModel(_connectionStrings.Value.DefaultConnection);
var user = await GetCurrentUserAsync();
RecAction RecAction = new RecAction();
RecAction.RecUser = RecordDataModel.GetRecord(user.Email, "Platform");
if (RecAction.RecUser.Record == null)
{
//Response Successfully Displayed
RecAction.Response = "No Record found";
}
return View(RecAction);
}
[HttpPost]
public async Task<IActionResult> PlatformRecord(RecAction RecActionNotUSed)
{
try
{
if (ModelState.IsValid)
{
var RecordDataModel = new RecordDataModel(_connectionStrings.Value.DefaultConnection);
var user = await GetCurrentUserAsync();
RecAction RecAction = new RecAction();
RecAction.RecUser = RecordDataModel.GetRecord(user.Email, "Platform");
RecSettings latestSettings = RecordDataModel.GetSettings();
RecKeys RecKeys = RecordDataModel.GetKey();
if (RecKeys.PrivateKey == null)
{
ModelState.Clear();
//Rsponse not updating
RecAction.Response = "NEED TO SHOW THIS RESPOSNE MESSAGE AFTER FORM POST.";
return View(RecAction);
}
return RedirectToAction("PlatformRecord");
}
Else
{
//Need to return the same view for errors so the validation is not overwritten.
return View();
}
}
catch
{
// If we got this far, something failed, redisplay form
return RedirectToAction("PlatformRecord");
}
}
}
VIEW:
#model ProjectXYZ.Models.RecordModels.RecAction
#{
ViewData["Title"] = "PlatformRecord";
}
<h2>Platform Record</h2>
<form asp-controller="RecordManager" asp-action="PlatformRecord" asp-route-returnurl="#ViewData["ReturnUrl"]" method="post" class="form-horizontal" autocomplete="off">
<p></p>
<hr />
<div class="form-group">
<label asp-for="RecUser.Record" class="col-md-2 control-label"></label>
<div class="col-md-10">
<textarea asp-for="RecUser.Record" class="form-control" cols="1" rows="8" readonly></textarea>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Response" class="text-danger" readonly/>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Request New Record</button>
</div>
</div>
</form>

How to pass a complex model back to the controller in asp.net mvc

New to web development.
I have a view that allows user to select an excel file.
When submit "preview" button is pressed file is read and data is sent back to the user to preview the data.
Then I want to be able send the model back to the control for db upload.
(this is the part I'm struggling with).
ViewModel:
public class UploadItemsViewModel
{
public List<Item> Items { get; set; }
public int CompanyID { get; set; }
public Company Company { get; set; }
public HttpPostedFileBase upload { get; set; }
public UploadJournalsViewModel()
{
Items = new List<Item>();
}
}
Controller:
public ActionResult Upload(FormCollection formCollection, int CompanyID)
{
if (Request != null)
{
HttpPostedFileBase file = Request.Files["UploadedFile"];
if ((file != null) && (file.ContentLength > 0) && !string.IsNullOrEmpty(file.FileName))
{
string fileName = file.FileName;
string fileContentType = file.ContentType;
byte[] fileBytes = new byte[file.ContentLength];
var data = file.InputStream.Read(fileBytes, 0, Convert.ToInt32(file.ContentLength));
}
}
UploadItemsViewModel itmViewModel = new UploadItemsViewModel { Company = db.Companies.Find(CompanyID), CompanyID = CompanyID };
return View(itmViewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Upload(UploadItemsViewModel itmViewModel, string Preview, string Upload)
{
if (ModelState.IsValid)
{
if (itmViewModel.upload != null && itmViewModel.upload.ContentLength >0)
{
try
{
itmlViewModel.Items = App.Services.ItemsMassUploadFileRead.ReadExcelFile(itmViewModel.upload, db.Companies.Find(itmViewModel.CompanyID));
if (string.IsNullOrEmpty(Preview))
{
foreach (var itm in itmViewModel.Items)
{
itm.StartDate = DateTime.Today;
itm.CompanyID = itmViewModel.CompanyID;
itm.User = null;
itm.Items.Add(itm);
db.SaveChanges();
}
return View();
}
else
{
return View(itmViewModel);
}
} }
catch (Exception ex)
{
ModelState.AddModelError("File", ex.Message.ToString());
return View(itmViewModel);
}
}
else
{
ModelState.AddModelError("File", "Please Upload Your file");
}
}
return View(itmViewModel);
}
View:
#using (Html.BeginForm("Upload", "ItemsUpload", null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.CompanyID)
<div class="form-group">
<div class="input-group">
<label class="input-group-btn">
<span class="btn btn-default">
Browse… <input type="file" style="display: none;" accept=".xlsx" name="upload">
</span>
</label>
<input type="text" class="form-control " readonly>
</div>
<span class="help-block">
Please use a provided Excel template
</span>
</div>
<div class="form-group">
<input type="submit" value="Preview" name ="Preview" class="btn btn-default" disabled style="display: none" id="submit"/>
</div>
<div class="form-group">
<input type="submit" value="Upload" name="Upload" class="btn btn-default" id="Upload" />
</div>
<div class="help-block" id="previewHelp" style="display: none">
Preview results and scroll down to upload data to the database.
</div>
if (Model.Journals.Count != 0)
{
table here to preview the upload
}
After clicking the Upload button model comes back without the "items" collection.
The Items list will be always null in the controller, because you don't rendered any input on the View with the name Items

how to convert formcollection to model in mvc

Is it possible to convert formcollection to a 'model' known?
[HttpPost]
public ActionResult Settings(FormCollection fc)
{
var model=(Student)fc; // Error: Can't convert type 'FormCollection' to 'Student'
}
NOTE : for some reasons i can't use ViewModel instead.
Here is my code VIEW: Settings.cshtml
#model MediaLibrarySetting
#{
ViewBag.Title = "Library Settings";
var extensions = (IQueryable<MediaLibrarySetting>)(ViewBag.Data);
}
#helper EntriForm(MediaLibrarySetting cmodel)
{
<form action='#Url.Action("Settings", "MediaLibrary")' id='MLS-#cmodel.MediaLibrarySettingID' method='post' style='min-width:170px' class="smart-form">
#Html.HiddenFor(model => cmodel.MediaLibrarySettingID)
<div class='input'>
<label>
New File Extension:#Html.TextBoxFor(model => cmodel.Extention, new { #class = "form-control style-0" })
</label>
<small>#Html.ValidationMessageFor(model => cmodel.Extention)</small>
</div>
<div>
<label class='checkbox'>
#Html.CheckBoxFor(model => cmodel.AllowUpload, new { #class = "style-0" })<i></i>
<span>Allow Upload.</span></label>
</div>
<div class='form-actions'>
<div class='row'>
<div class='col col-md-12'>
<button class='btn btn-primary btn-sm' type='submit'>SUBMIT</button>
</div>
</div>
</div>
</form>
}
<tbody>
#foreach (var item in extensions)
{
if (item != null)
{
<tr>
<td>
<label class="checkbox">
<input type="checkbox" value="#item.MediaLibrarySettingID"/><i></i>
</label>
</td>
<td>
<a href="javascript:void(0);" rel="popover" class="editable-click"
data-placement="right"
data-original-title="<i class='fa fa-fw fa-pencil'></i> File Extension"
data-content="#EntriForm(item).ToString().Replace("\"", "'")"
data-html="true">#item.Extention</a></td>
</tr>
}
}
</tbody>
CONTROLLER:
[HttpPost]
public ActionResult Settings(FormCollection fc)//MediaLibrarySetting cmodel - Works fine for cmodel
{
var model =(MediaLibrarySetting)(fc);// Error: Can't convert type 'FormCollection' to 'MediaLibrarySetting'
}
data-content and data- attributes are bootstrap popover.
Another approach in MVC is to use TryUpdateModel.
Example:
TryUpdateModel or UpdateModel will read from the posted form collection and attempt to map it to your type. I find this more elegant than manually mapping the fields by hand.
[HttpPost]
public ActionResult Settings()
{
var model = new Student();
UpdateModel<Student>(model);
return View(model);
}
Nice question!
Had same in the quest of making a universal base controller, model independent. Thanks to many people, the last one was #GANI, it's done.
Type ViewModelType is set in subclassed controller to anything you want.
public ActionResult EatEverything(FormCollection form)
{
var model = Activator.CreateInstance(ViewModelType);
Type modelType = model.GetType();
foreach (PropertyInfo propertyInfo in modelType.GetProperties())
{
var mykey = propertyInfo.Name;
if (propertyInfo.CanRead && form.AllKeys.Contains(mykey))
{
try
{
var value = form[mykey];
propertyInfo.SetValue(model, value);
}
catch
{
continue;
}
}
}
now that everything you received from an unknown form is in your real model you can proceed to validation from this post https://stackoverflow.com/a/22051586/7149454
You can try this way
public ActionResult Settings(FormCollection formValues)
{
var student= new Student();
student.Name = formValues["Name"];
student.Surname = formValues["Surname"];
student.CellNumber = formValues["CellNumber"];
return RedirectToAction("Index");
}
maybe it's too late, but maybe it will be useful for someone )
https://www.nuget.org/packages/tidago.apofc
Auto converter formcollection to object.
TestReadonlyModel resultObject = new TestReadonlyModel();
new ObjectPopulator().Populate(HttpContext.Request.Form, resultObject);

MVC5 One Click Delete from Details Page

I started with the scaffolding that VS MVC 5 can create, and it was working fine, but I wanted to be able to delete records ("Interviews", in this case) from the details page.
I started by copying the markup from the delete button on the Delete page over to Details, but it would simply redirect to the Details action. How can I get a button on the Details page to run the DeleteConfirmed method?
Here is the relevant code from the controller:
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Interview interview = db.Interviews.Find(id);
if (interview == null)
{
return HttpNotFound();
}
return View(interview);
}
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Interview interview = db.Interviews.Find(id);
db.Interviews.Remove(interview);
db.SaveChanges();
return RedirectToAction("Index");
}
public ActionResult Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Interview interview = db.Interviews.Find(id);
if (interview == null)
{
return HttpNotFound();
}
return View(interview);
}
and here is the markup that I copied from the Delete page and put into the Details view:
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
<div class="form-actions no-color">
<input type="submit" value="Delete" class="btn btn-danger" />
</div>
}
Here is the markup I needed to make it work:
#using (Html.BeginForm("Delete", "Interviews", new { id = Model.ID })) {
#Html.AntiForgeryToken()
<div class="form-actions no-color">
<input type="submit" value="Delete" class="btn btn-danger" />
</div>
}
You need to post to the DeleteConfirm action. Here, you're posting to the Details action because you're using just Html.BeginForm(). You need:
#using (Html.BeginForm("Delete", new { id = Model.Id })) {
You can get delete confirm by javascript on onclickling method like this
//change input type to button to prevent form submit
<input **type="button"** onClick="deleteConfirm" value="Delete" class="btn btn-danger" />
function deleteConfirm()
{
$.ajax({
url: "DeleteConfirmed Action Url",
type:"post",
data:{id:}
success: function(response) {
//check your server side result
}
});
}

"Controller, Action, Model" not all code paths return a value

I am really confused by this error "not all code paths return a value" on my action PostResponse. I have stared at my model, controller and view for hours and I think I have all paths covered. Of course the project won't build, so I can't debug further.
My action
// POST: /Questions/ViewQuestion/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult PostResponse([Bind(Include = "UserId,QuestionID,Answer,Source,Status,DateStamp")] Response response)
{
if (ModelState.IsValid)
{
db.Responses.Add(response);
db.SaveChanges();
}
}
My view
#model Template.Models.Question
#using Microsoft.AspNet.Identity
#{
ViewBag.Title = "View question";
var qtype = Model.QuestionTypeId;
ViewBag.Number = Model.Id - 7;
}
#using (Html.BeginForm("Question", "ViewQuestion", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
<div>
<h4>Question ##ViewBag.Number</h4>
<hr />
<h1> #Model.Question1</h1>
</div>
<div class="form-group">
#switch (qtype)
{
case 1:
// Textbox
#Html.TextArea("Answer", new { #class = "form-control", rows = "4", col = "5" });
break;
case 2:
// Dropdown
<select class="form-control" id="Answer">
#foreach (var item in Model.QuestionOptions.OrderBy(o => o.QuestionOptionRanking))
{
<option value="#item.QuestionOption1">#item.QuestionOption1</option>
}
</select>
break;
case 3:
// Checkbox
<div class="checkbox">
#foreach (var item in Model.QuestionOptions.OrderBy(o => o.QuestionOptionRanking))
{
<input type="checkbox" name="Answer" value="#item.QuestionOption1" /> #item.QuestionOption1 <br />
}
</div>
break;
case 4:
// Radio buttons
foreach (var item in Model.QuestionOptions.OrderBy(o => o.QuestionOptionRanking))
{
<div class="radio">
<label>
<input type="radio" name="Answer" value="#item.QuestionOption1" />
#item.QuestionOption1
</label>
</div>
}
break;
}
</div>
#using Template.Models.Response
#Html.HiddenFor(r => r.Responses, new { UserId = User.Identity.GetUserId(), Source = "Web", Status = "New", DateStamp = System.DateTime.Now })
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Answer" />
</div>
</div>
<br />
<hr />
<p>
#Html.ActionLink("Previous", "ViewQuestion", new { id = Model.Id - 1 }) |
#Html.ActionLink("Next", "ViewQuestion", new { id = Model.Id + 1 })
</p>
The page displays perfectly, but I can't test the post action as I cannot build with the current error.
Worked it out; but it was almost from scratch, as I created a new ViewModel and used that to populate the responses.
[HttpPost]
public ActionResult ViewQuestion([Bind(Include = "QuestionId, Answer, UserId")] ResponseViewModel responseViewModel)
{
Response re = new Models.Response();
re.Answer = responseViewModel.Answer;
re.UserId = responseViewModel.UserId;
re.QuestionId = responseViewModel.QuestionId;
re.DateStamp = System.DateTime.Now;
db.Responses.Add(re);
db.SaveChanges();
return RedirectToAction("ViewQuestion");
}
Thanks for your input as your comments got the old head working again. Thanks!
Your PostResponse action, or method specifies an ActionResult as a return type, but does not actually return anything. You can resolve this by changing it from ActionResult to void
try
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult PostResponse([Bind(Include = "UserId,QuestionID,Answer,Source,Status,DateStamp")] Response response)
{
if (ModelState.IsValid)
{
db.Responses.Add(response);
db.SaveChanges();
}
else{
return View("Error");
}
}

Resources