I'm building a blog website. I want users to be able to edit their posts.
I need to change the name of the HTML helper to match my model so I can use remote validation.
Model
[RegularExpression("[a-z]{1,50}", ErrorMessage = "URL String must be in lowercase")]
[Required(ErrorMessage = "Unique URL is required")]
[Remote("doesURLExist", "Post", HttpMethod = "POST",
ErrorMessage = "URL already exists. Please enter a different URL.")]
public string URLString { get; set; }
HTML, Using the viewbag to pass through my pre populated data.
<div class="form-group">
#Html.LabelFor(model => model.post.URLString, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.post.URLString, new { htmlAttributes = new { #Value = ViewBag.postURL, #class = "form-control", #name = "URLString" } })
#Html.ValidationMessageFor(model => model.post.URLString, "", new { #class = "text-danger" })
</div>
</div>
The prepopulated fields work great like this but my remote validation doesnt work. the name attribute needs to be "URLString" but it comes up as post.URLString which cant be used in my remote method.
Here is my remote method to check for existing URLStrings
[HttpPost]
public JsonResult doesURLExist(String URLString)
{
var allposts = _unitOfWork.PostRepository.Get();
if (allposts.Count(p => p.URLString == URLString) == 0)
{
return Json(true, JsonRequestBehavior.AllowGet);
}
return Json(false, JsonRequestBehavior.AllowGet);
}
I have gotten the remote validation to work using the raw HTML and manually changing the name attribute.
Here is the raw html that the helper outputs when I view source in google chrome. I copied it and changed the name.
<div class="form-group">
<label class="control-label col-md-2" for="post_URLString">URLString</label>
<div class="col-md-10">
<input class="form-control text-box single-line" data-val="true"
data-val-regex="URL String must be in lowercase" data-val-regex-pattern="[a-z]{1,50}"
data-val-remote="URL already exists. Please enter a different URL." data-val-remote-additionalfields="" data-val-remote-type="POST" data-val-remote-url="/Post/doesURLExist"
data-val-required="Unique URL is required" id="post_URLString" name="URLString" type="text" value= />
<span class="field-validation-valid text-danger" data-valmsg-for="URLString" data-valmsg-replace="true"></span>
</div>
</div>
So that works great! The issue is I cant use my viewbag to prepopulate the data.
So I guess I have 2 problems lets just solve the easier one.
1. How do i get model data in the HTML Value field. value = Model.post.URLString doesnt work.
How do I override the HTML name attribute #name = "URLString"
I'm pretty new to c# I might be missing something really obvious here.
Got it. Because the name of my form element kept have a post. before the name I couldn't use the URLString.
I instead pass a post object through and get the URL String that way.
I also pass through the ID for a more thorough check.
[HttpPost]
public JsonResult doesURLExist(tPost post)
{
var allposts = _unitOfWork.PostRepository.Get();
if (allposts.Count(p => p.URLString == post.URLString) == 0)
{
return Json(true, JsonRequestBehavior.AllowGet);
}
if (allposts.Count(p => p.URLString == post.URLString && p.Id == post.Id) == 1)
{
return Json(true, JsonRequestBehavior.AllowGet);
}
return Json(false, JsonRequestBehavior.AllowGet);
}
Here is my model, passing an additional field "Id" this also gets put into my post object.
[RegularExpression("[a-z, 0-9]{1,50}", ErrorMessage = "URL String must be in lowercase")]
[Required(ErrorMessage = "Unique URL is required")]
[Remote("doesURLExist", "Post", AdditionalFields = "Id", HttpMethod = "POST", ErrorMessage = "URL already exists. Please enter a different URL.")]
public string URLString { get; set; }
Here is my HTML, all working perfect now.
<div class="form-group">
#Html.LabelFor(model => model.post.URLString, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.post.URLString, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.post.URLString, "", new { #class = "text-danger" })
</div>
</div>
Related
I have been working on an ASP.NET MVC 5 web application using Entity Framework 6 as an assignment for my Business Programming II class. Despite the fact that I know very little about programming, I have been making progress, but I have run into trouble. I am supposed to write CRUD operations for an online storefront based on the Northwind Traders database. I already have working code for reading from the database as well as adding and updating items in the database. Where I'm struggling is deleting items. The following requirement is listed in the assignment description:
Delete a product by making it discontinued so that the information is displayed in the database. Do NOT actually delete a product from the database.
I've tried a couple things to try and make this work, but all have failed for various reasons.
Here's the code to my current Delete View (ignore any strange HTML formatting decisions, right now I'm focused on getting this functional):
#model NWTradersWeb.Models.Product
#{
ViewBag.Title = "Delete";
}
<h2>Delete</h2>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Product: #Html.DisplayFor(model => model.ProductName)</h4>
<hr />
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-actions">
<input type="submit" value="Yes" class="btn btn-dark" /> |
#Html.ActionLink("Back to List", "Index")
</div>
}
</div>
I have tried editing my ProductsController.cs to manually set the Discontinued attribute to true as follows:
public ActionResult Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Product product = db.Products.Find(id);
if (product == null)
{
return HttpNotFound();
}
return View(product);
}
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Product product = db.Products.Find(id);
product.Discontinued = true;
db.SaveChanges();
return RedirectToAction("Index");
}
This works, but if I run the Edit operation on the same product I'm unable to undo the change. I can deselect the Discontinued checkbox but it does not save after I submit the changes and the Index page still shows the product as discontinued.
Here's my code for the Edit View and corresponding ProductsController.cs methods, I'm unsure if these have anything to do with my problem but I will include them anyway:
View:
#model NWTradersWeb.Models.Product
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Product: #Html.DisplayFor(model => model.ProductName)</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.ProductID)
<div class="form-group">
#Html.LabelFor(model => model.SupplierID, "SupplierID", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("SupplierID", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.SupplierID, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.CategoryID, "CategoryID", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("CategoryID", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.CategoryID, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.QuantityPerUnit, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.QuantityPerUnit, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.QuantityPerUnit, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.UnitPrice, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.UnitPrice, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.UnitPrice, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.UnitsInStock, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.UnitsInStock, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.UnitsInStock, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.UnitsOnOrder, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.UnitsOnOrder, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.UnitsOnOrder, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ReorderLevel, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ReorderLevel, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ReorderLevel, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Discontinued, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<div class="checkbox">
#Html.EditorFor(model => model.Discontinued)
#Html.ValidationMessageFor(model => model.Discontinued, "", new { #class = "text-danger" })
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Controller Methods:
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Product product = db.Products.Find(id);
if (product == null)
{
return HttpNotFound();
}
ViewBag.CategoryID = new SelectList(db.Categories, "CategoryID", "CategoryName", product.CategoryID);
ViewBag.SupplierID = new SelectList(db.Suppliers, "SupplierID", "CompanyName", product.SupplierID);
return View(product);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "ProductID,ProductName,SupplierID,CategoryID,QuantityPerUnit,UnitPrice,UnitsInStock,UnitsOnOrder,ReorderLevel,Discontinued")] Product product)
{
if (ModelState.IsValid)
{
db.Entry(product).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CategoryID = new SelectList(db.Categories, "CategoryID", "CategoryName", product.CategoryID);
ViewBag.SupplierID = new SelectList(db.Suppliers, "SupplierID", "CompanyName", product.SupplierID);
return View(product);
}
My professor also alluded to making the Delete operation redirect to a simpler Edit page where we could toggle the Discontinued attribute. I think he may be alluding to a partial view but we have not covered that to my knowledge.
Please note: I consider myself a novice when it comes to programming. I've taken other classes but the instructors focused more on syntax than concepts and as such my foundation is incredibly weak. I might be clueless about certain things that other people take for granted. I want to go back and study the fundamentals after I graduate and self-study, but this is a required class for a degree that is almost completely unrelated to programming. Any tips, hints, even a nudge in the right direction would be greatly appreciated.
Your Delete logic seems fine. What I would look at in more detail is your Edit.
Overall I am not a fan of ever passing Entities between the server and the view, especially accepting an entity from the view. This is generally a bad practice because you are trusting the data coming from the view, which can easily be tampered with. The same when passing data from a view to server, this can lead to accidentally exposing more information about your domain (and performance issues) just by having some "sloppy" JavaScript or such converting the model into a JSON model to inspect client-side. The recent case of the journalist being accused of "hacking" because they found extra information via the browser debugger in a Missouri government website outlines the kind of nonsense that can come up when server-side code has the potential to send far too much detail to a browser.
In any case, in your Edit method when you accept the bound Product after deactivating the Discontinued flag, what values are in that Entity model? For instance if you use Delete to set Discontinued to "True", then go to the Edit view for that product and un-check that input control and submit the form, in your "product" coming in the Edit page, what is the state of the product.Discontinued?
If the value is still "True" then there is a potential problem with your page binding where the EditorFor is not linking to that flag properly or the value is not deserializing into the Product entity. (a private or missing setter?)
If it is coming back with what should be the correct value, then I would look at changing how you update entities. Code like this:
db.Entry(product).State = EntityState.Modified;
db.SaveChanges();
... is inherently dangerous as "product" is not an entity, it is a deserialized set of values used to populate an entity class. Ideally when updating data you would provide a ViewModel that won't be confused with an Entity class and contain just the fields that are allowed to be updated. Using your current code though with the entity class serving as that view model I would suggest something more like:
var dataProduct = db.Products.Single(x => x.Id == product.Id);
dataProduct.ProductName = product.ProductName;
dataProduct.Discontinued = product.Discontinued;
// ...
db.SaveChanges();
When it comes to possibly allowing the user to change FKs for things like categories, then you should eager load those relationships, compared the FK IDs then load and re-associate those "new" relationships in the entity loaded from data state. (Don't just replace the FK values.)
The reason for doing this rather than attaching and setting the state to modified:
We perform a validation when loading the entity. If we get back an Id that doesn't exist, we can handle that exception. We can also filter data to ensure that the current user actually has permission to see the requested ID and can end a session if it looks like someone is tampering with data.
We only update values that we allow to change, not everything in the entity. We can also validate to ensure that the values provided are fit for purpose before making changes.
When copying values across, EF will only generate UPDATE statements for values that actually change if any actually change. Attaching and setting the entity state to Modified or using Update will always generate an UPDATE statement replacing all values whether anything changed or not. (can have negative impacts on triggers or hooks in the DbContext for things like Auditing)
I am currently trying to get the validation from the backend models to the razor view using resource files. I have 2 languages setup, (en, fr) but even though French is selected, the error message is always in English.
I have tried multiple online tutorial / looking at other people having similar problem but none of the fixes worked for me.
my viewmodel example:
[Required(ErrorMessageResourceName = "RequiredField", ErrorMessageResourceType = typeof(GlobalRes))]
[Display(Name = "Username", ResourceType = typeof(GlobalRes))]
public string Username { get; set; }
[Required(ErrorMessageResourceName = "RequiredField", ErrorMessageResourceType = typeof(GlobalRes))]
[DataType(DataType.Password)]
[Display(Name = "Password", ResourceType = typeof(GlobalRes))]
public string Password { get; set; }
Razor view:
<div class="form-group">
#Html.LabelFor(m => m.Username, new { #class = "col-md-12" })
<div class="col-md-12">
#Html.TextBoxFor(m => m.Username, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Username, "", new { #class = "text-danger"})
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Password, new { #class = "col-md-12" })
<div class="col-md-12">
#Html.PasswordFor(m => m.Password, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Password, "", new { #class = "text-danger" })
</div>
</div>
Inspecting the html in your browser after changing the language and submitting the form.
<input class="input-validation-error form-control" data-val="true" data-val-required="Ce champ est requis." id="Username" name="Username" type="text" value="">
<span class="field-validation-error text-danger" data-valmsg-for="Username" data-valmsg-replace="true">This field is required.</span>
<input class="input-validation-error form-control" data-val="true" data-val-required="Ce champ est requis." id="Password" name="Password" type="password">
<span class="field-validation-error text-danger" data-valmsg-for="Password" data-valmsg-replace="true">This field is required.</span>
What the expected results should be is, based on the language selected, the error message should be in french. If you inspect the input box, you can see the correct value being added in french. When submitting the form, you actually only get the english value instead of the expected one.
Thank you in advance!
I had this problem also but I find a simple way, I controller add this code.
if (ModelState.IsValid)
{
}
else
{
ModelState.Clear();
TryValidateModel(Model);
}
I was having the exactly same issue. For me putting the resource files in App_GlobalResources folder solved my problem!
If that folder does not exist already you can create it.
I am using localization on Asp.Net MVC application.
I'm using an autogenerated form from visual studio for asp.net mvc 5 that saves information to a database. It's the create view with the standard scaffolding etc from asp.net with entity framework.
I like the way the form looks, but I need one field (datecreated) to at least be auto filled, (but preferably autofilled and hidden). The problem is I don't understand the autogenerated code at all and my efforts to look it up have not been successful. Nor have my efforts to understand it. I'm still a beginner with html helpers, which I think these are.
Here is the form element I am working with. The part in the middle is the part I need to change to autofill (the date created field), I think the relevant part is changing the EditorFor. but I don't know:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>New Patient:</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
... //other form items removed for simplicity
<div class="form-group">
#Html.LabelFor(model => model.DateCreated,"Date Created", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.DateCreated, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.DateCreated, "", new { #class = "text-danger" })
</div>
</div>
... //more items left out for simplicity
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
}
And the auto generated controller for this part looks like this:
// GET: Subjects/Create
public ActionResult Create()
{
return View();
}
// POST: Subjects/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,Name,DOB,Male,Female,Address,City,ZIP,PhoneHome,PhoneCell,Email,EmergencyContact,EmergencyContactPhone,EmergencyContactRelationship,ReferredBy,DateCreated,Allergy,AllergyDescription,HighBloodPressure,LowBloodPressure,HeartCondition,Diabetes,Anemia,HighCholesterol,Pacemaker,Epilepsy,Pregnant,Cancer,STD,Pain,PainDescription,Headache,HeadacheDescription,CommonCold,HighBloodPressureConcern,Stress,Depression,Sleep,Menstruation,Fertility,WeightControl,Other")] Subject subject)
{
if (ModelState.IsValid)
{
db.SubjectDatabase.Add(subject);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(subject);
}
If you dont know how I can autofill and or hide the form element datecreated, could you please point me to where I might learn to figure this out myself. I think I am reasonable at programming, I just don't understand html helpers well, or the bind function in the controller.
Remove this part from your View
<div class="form-group">
#Html.LabelFor(model => model.DateCreated,"Date Created", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.DateCreated, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.DateCreated, "", new { #class = "text-danger" })
</div>
</div>
And then, inside your Controller remove DateCreated from Bind attribute and assign DateCreated property:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,Name,DOB,Male,Female,Address,City,ZIP,PhoneHome,PhoneCell,Email,EmergencyContact,EmergencyContactPhone,EmergencyContactRelationship,ReferredBy,Allergy,AllergyDescription,HighBloodPressure,LowBloodPressure,HeartCondition,Diabetes,Anemia,HighCholesterol,Pacemaker,Epilepsy,Pregnant,Cancer,STD,Pain,PainDescription,Headache,HeadacheDescription,CommonCold,HighBloodPressureConcern,Stress,Depression,Sleep,Menstruation,Fertility,WeightControl,Other")] Subject subject)
{
if (ModelState.IsValid)
{
subject.DateCreated = DateTime.Now; //if you want UTC time, use DateTime.UtcNow
db.SubjectDatabase.Add(subject);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(subject);
}
The #Value can also be used to prefill in spots by using #Html.EditorFor:
Example:
#Html.EditorFor(c => c.Propertyname, new { #Value = "5" })
There is more information to be found at:
Html.EditorFor Set Default Value
I'm trying to get the selection from the drop-down list and save it into the database as its corresponding primary key.
When the user makes a selection, I am unable to use that selection. I think I am not collecting the correct information in my controller.
Here is my view:
<div class="form-group">
#Html.LabelFor(model => model.Type.TypeName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("Types", ViewBag.Types as SelectList, new { htmlAttributes = new { #class = "form-control" } } )
#Html.ValidationMessageFor(model => model.Type.TypeName, "", new { #class = "text-danger" })
</div>
</div>
Here is my controller
public ActionResult Create(CreateCommunicationViewModel commmodel){
...
var typeIDquery = db.Types.Where(g => g.TypeName == commmodel.Type.TypeName).Select(g => g.TypeID);
typeID = typeIDquery.AsEnumerable().FirstOrDefault().Trim().ToString();
I am not sure if you posted your complete code of your view but I think you are missing the HTML form:
#using (Html.BeginForm("Create", "YourControllerName", FormMethod.Get)) {....}
And in your Create method your parameter name has to match the name of dropdown list you are trying to catch:
public ActionResult Create(string Types){....}
The 'Types' parameter at your action method should be the selected value from the dropdown list.
I have a site in progress that is using ASP.Identity to create new users. I have a secondary table with more user information I would like to be populated once the account is registered. As I already have the id and email, I don't need to ask for these again but can't get the second form to pass the data through the Create Razor page, even though it's displaying on the form itself.
I'm getting an error message when I submit saying the "LoginID is required", so how do I get the form to post with the value of userLoginId?
Create Page
var userLoginId = User.Identity.GetUserId();
<div class="form-horizontal">
<h4>nrLogins</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.LoginID, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => userLoginId, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => userLoginId, "", new { #class = "text-danger" })
</div>
</div>
LoginController
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "LoginID,Email,FirstName,LastName,DateOfBirth,Address1,Address2,Address3,Address4,Address5,PostCode,PhoneMobile,PhoneOther,UserSearchable,SiteRoleType,AccountActive,AccountCreatedDate,AccountEditedDate,AccountDeletedDate")] dbLogin dbLogin)
{
if (ModelState.IsValid)
{
db.dbLogins.Add(dbLogin);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(dbLogin);
}
Thanks!
You need to specify the name attribute for the control otherwise it default to your variable name which is userLoginId instead of LoginId, so you can do the following:
#Html.EditorFor(model => userLoginId, null, "LoginId", new { htmlAttributes = new { #class = "form-control" } })
If you don't want the user to be able to see or edit the details you can pass it as a hidden field
#Html.Hidden("LoginId", userLoginId)