Updating column in ASP.NET mvc 5 creates new row - asp.net

I'm following this tutorial to create a modified version of a blog. In this case, the "posts" are the same things as "projects," the "tags" are called "technologies," and the comments are all the same. In this case, the create new post/project function also should be able to update existing posts/projects. When I click submit, however, editing an old post, it simply creates a new one.
Here is my controller:
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateInput(false)]
public ActionResult Update(int? p, string title, string shortDescription, string longDescription, DateTime dateTime, string technologies)
{
Project project = GetProject(p);
if (!User.IsInRole("ChapterAdvisor") || !(User.Identity.GetFirstName() + " " + User.Identity.GetLastName()).Equals(project.ProjectLeader))
{
RedirectToAction("Index");
}
project.Title = title;
project.ShortDescription = shortDescription;
project.LongDescription = longDescription;
project.TimeCreated = dateTime;
project.ProjectLeader = User.Identity.GetFirstName() + " " + User.Identity.GetLastName();
project.Technologies.Clear();
technologies = technologies ?? string.Empty;
string[] technologyNames = technologies.Split(new char[] {' '}, StringSplitOptions.RemoveEmptyEntries);
foreach (string technologyName in technologyNames)
{
project.Technologies.Add(GetTechnology(technologyName));
}
if (!p.HasValue)
{
model.Projects.Add(project);
}
try
{
model.SaveChanges();
}
catch (System.Data.Entity.Validation.DbEntityValidationException dbEx)
{
Exception raise = dbEx;
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
string message = string.Format("{0}:{1}",
validationErrors.Entry.Entity.ToString(),
validationError.ErrorMessage);
// raise a new exception nesting
// the current instance as InnerException
raise = new InvalidOperationException(message, raise);
}
}
throw raise;
}
return RedirectToAction("Details", new { p = project.Id });
}
public ActionResult Edit(int? p)
{
Project project = GetProject(p);
StringBuilder technologyList = new StringBuilder();
foreach (Technology technology in project.Technologies)
{
technologyList.AppendFormat("{0} ", technology.Name);
}
ViewBag.Technologies = technologyList.ToString();
return View(project);
}
private Technology GetTechnology(string technologyName)
{
return model.Technologies.Where(x => x.Name == technologyName).FirstOrDefault() ?? new Technology() { Name = technologyName };
}
private Project GetProject(int? id) => id.HasValue ? model.Projects.Where(x => x.Id == id).First() : new Project() { Id = -1 };
And this is my view:
<form action="#Href("~/Projects/Update")" method="post" id="postForm">
#Html.AntiForgeryToken()
#if (Model.Id != -1)
{
<input type="hidden" value="#Model.Id" />
}
#{ DateTime dateTime = Model.TimeCreated.Year > 2000 ? Model.TimeCreated : DateTime.Now; }
<input type="text" name="dateTime" value="#dateTime" /> Date<br />
<input type="text" name="title" value="#Model.Title" /> Project Name<br />
#Html.DropDownListFor(m => m.Technologies, new SelectList(new List<Object> { new { value = "Animation", text = "Animation" }, new { value = "Robotics", text = "Robotics" }, new { value = "Architecture", text = "Architecture" }, new { value = "CAD", text = "CAD" }, new { value = "Websites", text = "Websites" }, new { value = "Games", text = "Games" }, new { value = "Biotechnology", text = "Biotechnology" }, new { value = "Club", text = "Club" }, new { value = "Other", text = "Other" } }, "value", "text"), new { #style = "border: 1px solid #e8e8e8;padding: 0.5em 1.07em 0.5em;background: #f5f5f5;font-size: 0.875rem;border-radius: 5px;width: 100%;line-height: 1.43;min-height: 3.5em;" })
<textarea name="shortDescription" rows="5" cols="80">#Model.ShortDescription</textarea><br />
<textarea name="longDescription" rows="10" cols="80">#Model.LongDescription</textarea><br />
<input type="submit" name="submit" value="Save Changes" />
</form>
Any ideas why it is creating a new "project" instead of updating the one defined by the variable passed in the url?

Every post from that form is being treated as a "new" record because it doesn't contain the ID from an existing record. So the logic always assumes it's new.
This is because the hidden input isn't included in the POST data because it has no name:
<input type="hidden" value="#Model.Id" />
It looks like your action expects the ID value to be called "p":
<input type="hidden" name="p" value="#Model.Id" />

Related

Ajax Form returns null on CSV upload

The way this is setup is,
-There is a View which takes the CSV upload
-There is a Controller Partial View Action which is supposed to retrieve the parse the CSv and read the objects from the CSV and pass that back to the PArtial View.
-The Partial View is than SUPPOSED to render on the page with all the records.
But apparently the bulkClients object appears null.
Here is the Controller :-
public ActionResult UploadBulkClients()
{
return View();
}
// [HttpPost]
public PartialViewResult _UploadBulkClients(HttpPostedFileBase bulkClients)
{
if (bulkClients != null)
{
try
{
StreamReader reader = new StreamReader(bulkClients.InputStream);
while (reader != null)
{
var csv = new CsvReader(reader);
csv.Read();
csv.ReadHeader();
while (csv.Read())
{
newRecord.Add(new ClientAgencyViewModel()
{
Id = UrbanLighthouse.Shared.Statics.NewUniqueGUID(),
ReferenceNo = csv["ReferenceNo"].ToString(),
FirstName = csv["FirstName"].ToString(),
MiddleName = csv["MiddleName"].ToString(),
LastName = csv["LastName"].ToString(),
StreetAddress = csv["StreetAddress"].ToString(),
City = csv["City"].ToString(),
PostalCode = csv["PostalCode"].ToString(),
Province = Guid.Parse(csv["Province"].ToString()),
Phone = csv["Phone"].ToString(),
Email = csv["Email"].ToString()
});
}
foreach (var item in newRecord)
{
if (repository.DoesEmailExist(item.Email) == true)
{
item.Email = item.Email + " : " + "Invalid Email Address";
}
else
{
item.Email = item.Email + " : " + "This Email is Good";
}
}
}
return PartialView(newRecord);
}
catch (System.IO.IOException e)
{
return PartialView(e);
}
}
else
{
newRecord.Add(new ClientAgencyViewModel()
{
ReferenceNo = "Empty",
FirstName = "Empty",
MiddleName = "Empty",
LastName = "Empty",
StreetAddress = "Empty",
City = "Empty",
PostalCode = "Empty",
Province = Guid.Empty,
Phone = "Empty",
Email = "Empty"
});
return PartialView(newRecord);
}
}
Here is how the View is layed out :-
#model string
#{
Layout = "~/Views/Shared/_LayoutAnonymous.cshtml";
AjaxOptions options = new AjaxOptions
{
UpdateTargetId = "uploadList",
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST"
};
}
<div>
#using (Ajax.BeginForm("_UploadBulkClients", "Client",options, new { enctype = "multipart/form-data" , role = "form", #class = Css.Form, #id = "formLogin" , action = "/Client/_UploadBulkClients" }))
{
<div class="#Css.FormGroup">
<h1>Client Bulk Upload</h1>
<div class="#Css.InputGroup">
<label>Upload CSV File</label>
<input type="file" name="postedFile" />
</div>
<div class="#Css.InputGroup">
#Html.Submit("Submit")
</div>
</div>
}
</div>
<div>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Reference No</th>
<th>FirstName</th>
<th>MiddleName</th>
<th>LastName</th>
<th>Phone</th>
<th>Email</th>
<th>StreetAddress</th>
<th>City</th>
<th>PostalCode</th>
<th>Province</th>
</tr>
</thead>
<tbody id="uploadList">
#Html.Action("_UploadBulkClients","")
</tbody>
</table>
</div>
When the HttpPost decorator is left on top of the PartialView Method, it gives an Error in the View #Html.Action("_UploadBulkClients","") saying that the _UploadBulkClients Action method does not exist.
EDIT:-
It seems that the upload is not posting the csv file, and I would not understand why that would be, as the Ajax form seems to be the way it is supposed to be.
Any help would be appreciated !
With the help of Stephen, I was able to figure out the answer, it had to be done using JavaScript and not AjaxOptions in C#.
Here are the steps just in case someone encounters something similar :-
1. The Controller Method which returns the form
public ActionResult UploadBulkClients()
{
return View();
}
2. The form has to be a simple form and not an Ajax form. I used Html Helpers here:
#using (Html.BeginForm("_UploadBulkClients", "Client",null, FormMethod.Post , new { enctype = "multipart/form-data" , role = "form", #class = Css.Form, #id = "uploadForm" }))
{
<div class="#Css.FormGroup">
<h1>Client Bulk Upload</h1>
<div class="#Css.InputGroup">
#Html.LabelFor(m=>m.File)
#Html.TextBoxFor(m=>m.File, new { type= "file"})
</div>
<div class="#Css.InputGroup">
#Html.Submit("Submit")
</div>
</div>
}
3. The next part is the controller method which return the partial view.
[HttpPost]
public PartialViewResult _UploadBulkClients(HttpPostedFileBase file)
{
if (file != null)
{
try
{
using (var reader = new StreamReader(file.InputStream))
using (var csv = new CsvReader(reader))
{
csv.Read();
csv.ReadHeader();
while (csv.Read())
{
newRecord.Add(new ClientAgencyViewModel()
{
Id = UrbanLighthouse.Shared.Statics.NewUniqueGUID(),
ReferenceNo = csv["ReferenceNo"],
FirstName = csv["FirstName"].ToString(),
MiddleName = csv["MiddleName"].ToString(),
LastName = csv["LastName"].ToString(),
StreetAddress = csv["StreetAddress"].ToString(),
City = csv["City"].ToString(),
PostalCode = csv["PostalCode"].ToString(),
ProvinceText = csv["Province"].ToString(),
Phone = csv["Phone"].ToString(),
Email = csv["Email"].ToString()
});
}
foreach (var item in newRecord)
{
if (repository.DoesEmailExist(item.Email) == true)
{
item.Email = item.Email + " : " + "Email Address Already Exists";
}
else
{
item.Email = item.Email + " : " + "This Email is Good";
}
}
}
return PartialView(newRecord);
}
catch (System.IO.IOException e)
{
return PartialView(e);
}
}
else
{
newRecord.Add(new ClientAgencyViewModel()
{
ReferenceNo = "Empty",
FirstName = "Empty",
MiddleName = "Empty",
LastName = "Empty",
StreetAddress = "Empty",
City = "Empty",
PostalCode = "Empty",
ProvinceText = "Empty",
Phone = "Empty",
Email = "Empty"
});
return PartialView(newRecord);
}
}
4. The next part is the partial View which renders the output from the controller method.
#model List<WebPlatform.ViewModels.ClientAgencyViewModel>
#foreach (var item in Model)
{
<tr>
<td>#item.ReferenceNo</td>
<td>#item.FirstName</td>
<td>#item.MiddleName</td>
<td>#item.LastName</td>
<td>#item.Phone</td>
<td>#item.Email</td>
<td>#item.StreetAddress</td>
<td>#item.City</td>
<td>#item.PostalCode</td>
<td>#item.ProvinceText</td>
</tr>
}
5. And last but not the least the Ajax functionality written within the JavaScript because AjaxOptions will not work in this particular situation.
$('#uploadForm').submit(function(e) {
e.preventDefault();
var formdata = new FormData($(this).get(0));
$.ajax({
url: '#Url.Action("_UploadBulkClients","Client")',
type: 'POST',
data: formdata,
processData: false,
contentType: false,
success: function (response) {
$('#uploadList').html(response);
}
},
);
});

System.NotSupportedException was unhandled by user code, LINQ to Entities does not recognize the method

I am new in MVC asp.net i am developing a project where i want to add floors sequence wise. Want to start sequence from 0.
Message=LINQ to Entities does not recognize the method 'NTB.Floor LastOrDefault[Floor](System.Linq.IQueryable1[NTB.Floor], System.Linq.Expressions.Expression1[System.Func`2[NTB.Floor,System.Boolean]])' method, and this method cannot be translated into a store expression.
Controller:
public ActionResult Create(int id)
{
var cfloorno = 0;
//if (bid > 0)
//{
var lastfloor = db.Floors.Last(x => x.BuildingId == id);
if (lastfloor != null)
{
cfloorno = lastfloor.FloorNo.GetValueOrDefault() + 1;
}
//}
var building = db.Buildings.ToList();
ViewBag.Building = building;
var type = db.FloorTypes.ToList();
ViewBag.Type = type;
ViewBag.CurrentFloor = cfloorno;
ViewBag.buildingid = id;
return View();
}
[HttpPost]
public ActionResult Create(Floor f)
{
using (db)
{
db.Floors.Add(f);
db.SaveChanges();
}
return RedirectToAction("List");
}
View:
<input type="hidden" name="BuildingId" value="#ViewBag.buildingid" />
<div class="row">
<label class="col-sm-2 control-label">Floor #</label>
<div class="col-sm-10">
<input class="form-control" name="FloorNo" value="#ViewBag.CurrentFloor" disabled type="number">
#Html.ValidationMessageFor(model => model.FloorNo)
</div>
</div>

How to render input elements inside form elements

Antaris RazorEngine v3.9.3 is used in ASP.NET MVC4 controller to create form element.
In result html input elements are rendered outside form element.
For example
#using (Html.BeginForm("Test", "Upload"))
{
<input type="file" />
<input type="submit"/>
}
produces
<form action="/Upload/Test" method="post"></form>
<input type="file" />
<input type="submit"/>
while correct result should be
<form action="/Upload/Test" method="post">
<input type="file" />
<input type="submit"/>
</form>
RenderScript returns only input elements. <form action="/Upload/Test" method="post"> is written to output directly. It looks like BeginForm() writes directly to view and Razorengine does not capture its output.
How to fix this ?
View:
#Model.RenderScript()
Model:
public IHtmlString RenderScript() {
var config = new TemplateServiceConfiguration();
config.BaseTemplateType = typeof(HtmlTemplateBase<>);
config.Namespaces = new HashSet<string>(new string[] {
"System",
"System.Collections",
"System.Collections.Generic",
"System.Globalization",
"System.Linq",
"System.Web",
"System.Web.Mvc",
"System.Web.Mvc.Html"
});
razor = RazorEngineService.Create(config);
// In real application colModel is retrieved from database
var colModel=#"
#using (Html.BeginForm(""Test"", ""Upload""))
{
<input type='file' />
<input type='submit'/>
}
";
string res = razor.RunCompile(colmodel, colmodel, typeof(object), new object());
return MvcHtmlString.Create(res);
}
Template base class:
using RazorEngine.Templating;
using RazorEngine.Text;
using System;
using System.IO;
using System.Web;
using System.Web.Mvc;
using System.Web.WebPages;
public class HtmlTemplateBase<T> : TemplateBase<T>, IViewDataContainer
{
// http://stackoverflow.com/questions/8561164/razorengine-issues-with-html
HtmlHelper<T> helper = null;
ViewDataDictionary viewdata = null;
public HtmlHelper<T> Html
{
get
{
// https://github.com/Antaris/RazorEngine/issues/150
if (helper == null)
{
var p = WebPageContext.Current;
var wvp = p.Page as WebViewPage;
var context = wvp != null ? wvp.ViewContext : null;
context.Writer = this.CurrentWriter;
helper = new HtmlHelper<T>(context, this);
}
return helper;
}
}
public ViewDataDictionary ViewData
{
get
{
if (viewdata == null)
{
viewdata = new ViewDataDictionary();
viewdata.TemplateInfo = new TemplateInfo() { HtmlFieldPrefix = string.Empty };
if (this.Model != null)
{
viewdata.Model = Model;
}
}
return viewdata;
}
set
{
viewdata = value;
}
}
public override void WriteTo(TextWriter writer, object value)
{
if (writer == null)
throw new ArgumentNullException("writer");
if (value == null) return;
//try to cast to RazorEngine IEncodedString
var encodedString = value as IEncodedString;
if (encodedString != null)
{
writer.Write(encodedString);
}
else
{
var htmlString = value as IHtmlString;
if (htmlString != null) writer.Write(htmlString.ToHtmlString());
else
{
encodedString = TemplateService.EncodedStringFactory.CreateEncodedString(value);
writer.Write(encodedString);
}
}
}
}
Posted also in https://github.com/Antaris/RazorEngine/issues/443

Asp.net MVC, Pass Array to Controller From View Without Using Ajax

I am Using asp.net mvc 5. I want to pass array of string from view to controller without using Ajax. Can Anyone help?
This is the controller, value is to be gotten in packagelist[]
public ActionResult Create(Business business, string loc, string serv, string[] packagelist)
{
try
{
if (ModelState.IsValid)
{
var a = locationIds;
business.ServiceId = db.Services.Where(x => x.Title ==serv).Select(x => x.Id).SingleOrDefault();
business.LocationId = db.Locations.Where(x => (x.Title + " (" + x.State + "), " + x.PostalCode)==loc).Select(x => x.Id).SingleOrDefault();
db.Businesses.Add(business);
db.SaveChanges();
return RedirectToAction("Index");
}
else
{
foreach(var error in ModelState.Values)
{
foreach(var er in error.Errors)
ModelState.AddModelError("", er.Exception.ToString());
}
}
ViewData["Packages"] = db.BusinessPackages.Select(x => new SelectListItem
{
Text = x.Package,
});
return View(business);
}
catch(Exception ex)
{
ModelState.AddModelError("", ex);
return View(business);
}
}
Have created an hidden input on view
<input type="hidden" name="packagelist" id="packagelist" value="" />
Then set the value of this hidden field via this function
$("#theform").submit(function (e) {
e.preventDefault();
var locdiv=$("#maindiv");
var locations = locdiv.children();
var loc = [];
for (var i = 0; i < locations.length; i++)
{
loc.push(locations.eq(i).text());
}
// SaveLocations(loc);
$("#packagelist").val(loc);
$("#theform").submit();
Now main problem is that when I set value of the input via Jquery and submit it to the controller, controller is considering the array of values as single value.
To receive an array as param in controller, your inputs need specific naming
<input type="hidden" name="packagelist[0]" id="packagelist_0_" value="" />
<input type="hidden" name="packagelist[1]" id="packagelist_1_" value="" />
<input type="hidden" name="packagelist[2]" id="packagelist_2_" value="" />
...
<input type="hidden" name="packagelist[x]" id="packagelist_x_" value="" />
I think you can use cookies.
In JS,
document.cookie = "CookiesName=" + value + "; " + "365;path=/";
In Controller,
var data = Request.Cookies["CookiesName"]

ASP.NET MVC4 + Get drop down value after HttpPost submit

So I want to get a value of the dropdown (== id of the account) to insert a new transaction
Here below you can find my View:
<div id="content">
<h2>Overschrijving</h2>
<p id="topmsg">velden met een * zijn verplicht</p><p> </p>
<dl class="clearfix form">
#using (Html.BeginForm(new { ReturnUrl = ViewBag.ReturnUrl })) {
#Html.ValidationSummary(true, "Gelieve alles correct in te vullen.")
<dt>#Html.Label("Rekening: *")</dt>
<dd>#Html.DropDownList("ddlAccounts")</dd>
<dt>#Html.Label("Begunstigde: *")</dt>
<dd>#Html.TextBox("ToAccountNumber")</dd>
<dt>#Html.Label("Bedrag: *")</dt>
<dd>#Html.TextBox("Amount", null, new { #class = "inpshort" })</dd>
<dt>#Html.Label("Mededeling: ")</dt>
<dd>#Html.TextBox("Message", null, new { #class = "inplong" })</dd>
<dd class="buttons">
<input type="submit" value="Bevestigen" />
<input type="submit" value="Annuleren" />
</dd>
}
</dl>
</div>
this is the Controller code:
public ActionResult Transfer(int? id) {
Holder h = Holder.GetHolderByHolderId(Convert.ToInt32(User.Identity.Name));
List<Account> holderAccounts = Account.GetAccountsByHolderId(h.Id);
List<SelectListItem> ddlHolderAccounts = new List<SelectListItem>();
ddlHolderAccounts.Add(new SelectListItem { Text = "selecteer...", Value = "-1", Selected = true });
foreach (Account acc in holderAccounts) {
if (acc.Id == id) {
ddlHolderAccounts.Add(new SelectListItem { Text = acc.Name, Value = acc.Id.ToString(), Selected = true });
} else {
ddlHolderAccounts.Add(new SelectListItem { Text = acc.Name, Value = acc.Id.ToString() });
}
}
ViewData["ddlAccounts"] = ddlHolderAccounts;
return View();
}
[HttpPost]
public ActionResult Transfer(Transaction tra) {
if (ModelState.IsValid) {
Transaction.InsertTransaction(tra.Amount, tra.Message, tra.FromAccountId, tra.ToAccountNumber);
}
return View(tra);
}
Now I searched a lot with Google, it's probably better to use the DropDownListFor to fill your drop down list? But could anyone show me an example?
By looking at your code, I can see that you're not passing a list of SelectListItems to the DropDownList helper. You can do this one of two ways.
1- Bind it to a property on your model:
#Html.DropDownListFor(x=>Model.Property, new List<SelectListItem> { new SelectListItem { Text = "Item1", Value = "Value1" })
Or
2- You can do it without binding to a model property like:
#Html.DropDownList("propertyName", new List<SelectListItem> { new SelectListItem { Text = "Item1", Value = "Value1" } })
If you're using the second approach then your controller action must accept "propertyName" as a parameter when submitting.
And don't forget to provide a list of SelectListItems to select from (which you're not doing in your code).
Hope this helps.
should be
#Html.DropDownListFor(x=>Model.Property, new List { new SelectListItem { Text = "Item1", Value = "Value1" }})

Resources