I have a view that has a textbox to enter a user name and then two checkboxes. It will be use to add said user name to the roles marked by the checkboxes. The text box should not allow empty/null strings to be entered and if the user name already exists, warn the person.
View
#model QIEducationWebApp.Models.UserAdmin
<h1 class="page-header">Add New User</h1>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<table class="table">
<tr>
<th class="table-row">
User Name:
</th>
<td class="table-row">
#Html.TextBoxFor(model => model.UserName)
#Html.ValidationMessageFor(model => model.UserName)
</td>
</tr>
<tr>
<th class="table-row">
Role:
</th>
<td class="table-row">
#Html.DropDownListFor(model => model.UserRole,
#ViewBag.Roles as SelectList, " -- Select Role -- ", new { #class="form-control" })
#Html.ValidationMessageFor(model => model.UserRole)
</td>
</tr>
<tr><td class="table-row-blank"></td></tr>
<tr>
<td class="table-row-button">
<input class="button" type="submit" value="Create" />
<input type="button" class="button" value="Cancel"
onclick="location.href='#Url.Action("AllUsers")'" />
</td>
</tr>
</table>
}
Model
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[Required(ErrorMessage = "Required")]
[Remote("ExistUser", "Admin", HttpMethod = "POST",
ErrorMessage = "User is assinged, Edit instead")]
[DataMemberAttribute()]
public global::System.String UserName
{
get
{
return _UserName;
}
set
{
OnUserNameChanging(value);
ReportPropertyChanging("UserName");
_UserName = StructuralObject.SetValidValue(value, false);
ReportPropertyChanged("UserName");
OnUserNameChanged();
}
}
private global::System.String _UserName;
partial void OnUserNameChanging(global::System.String value);
partial void OnUserNameChanged();
Controller validation method
[HttpPost]
public JsonResult ExistUser(String UserName)
{
bool exist = db.UserAdmins.Any(u => u.UserName == UserName);
return Json(!exist);
}
Controller post method
[Authorize(Roles = "Director")]
[HttpPost]
public ActionResult AddNewUser(UserAdmin user)
{
if (ModelState.IsValid)
{
db.UserAdmins.AddObject(user);
db.SaveChanges();
return RedirectToAction("AllUsers");
}
ViewBag.Roles = new SelectList(db.UserRoles, "UserRoleID", "UserRole1", user.UserRole);
return View(user);
}
Currently it allows empty strings to be passed in on submit instead of showing the error. And my custom validation isn't even firing off and my debug is getting hit.
I have used this in other parts of the application and those still work.
P.S. If you guys need more code, just let me know and I'll get it up.
EDIT:: Completely different code than the original post.
Volkan Paksoy pointed out my missing ModelState.IsValid and view return.
And for the client side validation i was missing my
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
First off, as #Chris Bohatka suggested, you have to use your model as the parameter (User class in this case)
Then you have to check the status of the ModelState like this:
if (!ModelState.IsValid)
{
// There is an error on the page, load the same view to display the errors
}
// Model is fine, carry on with the other stuff...
I debugged and saw the "Must enter a User Name" in ModelState errors collection so what you have to do is return to the same view so that user can see the error and fix it.
Maybe you do not have client side validation enabled, in which case it will do the post even if the fields are not correct. Also, in your post controller method you aren't checking validity of ModelState, which may be invalid, meaning there's a validation issue you aren't trapping for. Add this to your controller method:
if (ModelState.IsValid) { do work };
And make sure you have these lines in web.config to enable client side (JavaScript) validation, in which case the post won't happen until the field values are valid:
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
Related
I try to pass value from 'class' in the View to controller and save it to database. Date picked by the user is stored in class 'finalDate' (about 100 rows with such date field and 'Save' button):
View:
<tr>
<td>
#Html.DisplayFor(modelItem => item.custName)
</td>
<td class="choice">
<input class="finalDate" type="date">
</td>
<td>
#Html.ActionLink("Save","Update", new { cust = item.custName, manualDate = "{finalDate.value}" }) //can I pass class (or id) in that way?
</td>
</tr>
Controller:
public ActionResult Update(string cust, DateTime? manualDate)
{
_docs.UpdateData(cust, manualDate); //method UpdateData takes args and saves changes to db
return RedirectToAction("Index");
}
The issue is that no error shows up. Nothing has been changed to db anyway. So I'm wondering if:
I pass class value to ActionLink correctly (and is it possible at all)?
I need to wrap up my View code in <form action="#Url.Action("ActionName", "Home"))" method="post"> to send data to server?
Add [HttpPost] to ActionResult?
I am trying to build a simple CMS which allows the author to upload files (specifically images but the file type is not really important for now).
The upload of the file is working fine. However I want to provide the ability to list and subsequently delete a file (maybe later multiple files but for now a single file at a time is fine).
I have looked around the net. I see plenty of examples using EF to store the location of the file in a DB because they have permissions and roles etc. While that is something I may need way off in the future, its not a layer of complexity I am willing to add right now.
All I want is to simply press a delete link (just as though you are deleting a record in a DB). To trigger an action which calls a delete confirmation view. Then on that view, a delete button to actually delete the file and return the user to the list. Below is my code so far:
This would be the view that lists the files:
#model IEnumerable<FileInfo>
#{
ViewBag.Title = "File List";
}
<h2>Index</h2>
<p>
#Html.ActionLink("Upload", "Upload")
</p>
<table class="table">
<tr>
<th>File Name</th>
<th>Actions</th>
</tr>
#foreach (FileInfo file in Model)
{
<tr>
<td>#file.Name</td>
<td>#Html.ActionLink("Delete", "Delete", new { fileName = #file.Name })</td>
</tr>
}
</table>
I wont show the controller for this view as it's relatively simple and not where I am having the problem (I think). I only showed this so you could see the delete link and tell me if there is anything wrong.
Below is the delete confirmation view:
#model FileInfo
#{
ViewBag.Title = "Delete";
}
<h2>Delete</h2>
<h3>Are you sure you want to delete this?</h3>
<dl class="dl-horizontal">
<dt>
#Html.DisplayNameFor(model => model.FullName)
</dt>
<dd>
#Html.DisplayFor(model => model.FullName)
</dd>
</dl>
#using (Html.BeginForm("Delete", "FileManagement", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<div class="form-actions no-color">
#Html.ActionLink("Back to list of views", "Index", null, new { #class = "btn btn-success" })
|
#*#Html.ActionLink("Delete", "Delete", null, new { #class = "btn btn-danger" })*#
<input type="submit" value="Delete file" formaction="Delete" formmethod="delete" class="btn btn-danger" />
</div>
}
Below are the two Delete actions (GET and POST / DELETE)
// GET: FileManagement/Delete/filename
public ActionResult Delete()
{
return View();
}
// POST: FileManagement/Delete/filename
[HttpDelete]
[ValidateAntiForgeryToken]
public ActionResult Delete(string fileName)
{
var path = Path.Combine(Server.MapPath("~/UserFiles"), fileName);
if (System.IO.File.Exists(path))
System.IO.File.Delete(path);
else
return HttpNotFound();
return RedirectToAction("Index");
}
I don't have view models as I am not connecting to a database (yet). The files are just uploaded to the folder ~/UserFiles/someFileName.ext and the full path is got through appending this to the server.mappath in the normal way.
The problem I am having is getting the file name into the delete confirmation view, and also into the delete button which would pass it to the delete action to do the job.
Thanks for any help.
In your main view (I assume that Index.cshtml), you correctly generate a query string value for the fileName, but the GET method does not have a parameter to accept it. It would need to be
// GET: FileManagement/Delete/filename
public ActionResult Delete(string fileName)
and in that method you would need to initialize a new FileInfo class based on the fileName and pass that model to the view.
The next issue is that your form in the confirm page does not pass the file name back to the POST method, but that raises another issue in that you cannot have a GET and POST method with the same signatute, so you would need to change the name of one of the methods, for example
[HttpGet]
public ActionResult ConfirmDelete(string fileName)
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(string fileName)
and in the confirm delete page, change the form to
#using (Html.BeginForm("Delete", "FileManagement", new { fileName = Model.Name })) // enctype not required
{
#Html.AntiForgeryToken()
<input type="submit" value="Delete file" class="btn btn-danger" />
}
However, you can greatly improve performance by generating the form in the Index view and displaying a confirm dialog (the GET method is no longer required)
#foreach (FileInfo file in Model)
{
....
#using(Html.BeginForm("Delete", "FileManagement", new { fileName = file.Name }))
{
#Html.AntiForgeryToken()
<input type="submit" value="delete" />
}
}
and adding a script to display the dialog
$('form').submit(function() {
return conform("Are your sure .... ");
});
which will display the browsers javascript confirm dialog. You can further enhance the UI by using a jquery plugin for the confirm dialog (or implement your own, as explained in this article)
You should also consider using ajax to submit the form (and in the success callback, remove the button and its associated table row). A typical implementation might look like
#foreach (FileInfo file in Model)
{
<tr>
<td>#file.Name</td>
<td>
<form class="deleteform">
#Html.AntiForgeryToken()
<input type="hidden" name="fileName" value="#file.Name" />
<input type="submit" value="delete" />
</form>
</td>
</tr>
}
var url = '#Url.Action("Delete", "FileManagement")';
$('.deleteform').submit(function() {
var formData = $(this).serialize();
var row = $(this).closest('tr');
$.post(url, formData, function(response) {
if (response) {
row.remove();
} else {
// Oops - display message?
}
}).fail(function (response) {
// Oops
});
return false; // cancel the default submit
});
and the controller method
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(string fileName)
{
.... // delete the file
return Json(true); // indicate success
// or return Json(null); to indicate failure
}
I'm very new to MVC and I'm trying to figure out if there is a better way to do this. I have a textbox for the user to put in their search, then based on that search I am displaying some results below said search box. I am trying to avoid having so much code logic in my view and would like to know if there is a better way of handling this. Here is my existing code, where based on what the value of "Model.Results" is it will return one of 3 partial views or a button if the rest of my logic passes:
#section CustomerPrefixInfo
{
#if (Model.Results == PrefixSearch.SearchResults.CustomerFound)
{
#Html.Partial("_CustomerPrefixInfo")
}
#if (Model.Results == PrefixSearch.SearchResults.PrefixResultsFound)
{
#Html.Partial("_PrefixResults")
}
#if (Model.Results == PrefixSearch.SearchResults.AnimalsFound)
{
#Html.Partial("_AnimalSearchResults")
}
#if (Model.Results == PrefixSearch.SearchResults.ValidNewPrefix)
{
using (Html.BeginForm("Index", "PrefixManagement", new { prefix = Model.AnimalPrefix.Prefix, dbPrefix = Model.AnimalPrefix.DbPrefix }))
{
<fieldset>
<input id="btnReservePrefix" type="submit" value="Reserve Prefix" />
</fieldset>
}
}
}
I would like to put this inside a controller so that it just returns the view that is to be displayed, then just display that view on the page. Aftering doing some rearch I thought using Ajax.BeginForm with the InsertionMode set to InsertAfter would do the trick:
#using (Ajax.BeginForm("GenericSearch", "Home", FormMethod.Post, new AjaxOptions { InsertionMode = InsertionMode.InsertAfter, UpdateTargetId = "searchResults" }))
{
<fieldset>
<input id="btnPrefixSearch" type="submit" value="Prefix Search/Validate"/>
#Html.EditorFor(model => model.Input)
</fieldset>
<div id="searchResults">
</div>
}
My GenericSearch Action then uses a switch to decide which partial view to return:
public ActionResult GenericSearch(PrefixSearch prefixSearch)
{
//some database logic here to get the results
switch (prefixSearch.Results)
{
case PrefixSearch.SearchResults.CustomerFound:
return PartialView("_CustomerPrefixInfo", prefixSearch);
case PrefixSearch.SearchResults.PrefixResultsFound:
return PartialView("_PrefixResults", prefixSearch);
case PrefixSearch.SearchResults.AnimalsFound:
return PartialView("_AnimalSearchResults", prefixSearch);
default:
return null;
}
}
But when I tried this it puts the partial view on a new page.
here is one of my partial views (they are all 3 mostly identical to this)
#model MVC_Test_Project.Models.PrefixSearch
#{
ViewBag.Title = "PrefixResults";
}
#{
Layout = null;
}
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.PrefixResults[0].Prefix)
</th>
<th>
#Html.DisplayNameFor(model => model.PrefixResults[0].CustomerCount)
</th>
<th>
#Html.DisplayNameFor(model => model.PrefixResults[0].Link)
</th>
</tr>
#foreach (var item in Model.PrefixResults)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Prefix)
</td>
<td>
#Html.DisplayFor(modelItem => item.CustomerCount)
</td>
<td>
edit
</td>
</tr>
}
</table>
Any help would be appreciated!
Edit Just a helpful hint in case anybody makes the same stupid mistake I did, make sure your bundles are called before your scripts.
#Styles.Render("~/Content/css")
#Scripts.Render("~/bundles/modernizr")
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/jqueryval")
#Scripts.Render("~/bundles/bootstrap")
<script src="/Scripts/jquery.unobtrusive-ajax.js"></script>
<script src="/Scripts/jquery.unobtrusive-ajax.min.js"></script>
I had added in those last 2 lines when it was mentioned to use those, but they were above my bundles....and thus the ajax didn't work because of it. Thanks for everybodies help, all is well now!
--Joseph
I would like to put this inside a controller so that it just returns the view
Seems pretty straight forward:
ViewModel:
public class MyModel
{
public string PageToRender { get; set; }
}
Controller Action
public ActionResult DoLogic()
{
//var Results = ?? as The ENum
switch(Results)
{
PrefixSearch.SearchResults.CustomerFound:
model.PageToRender = "_CustomerPrefixInfo";
// etc etc
}
return View(model);
}
View:
#{if (!string.IsNullOrEmpty(model.PageToRender))
Html.RenderPartial(model.PageToRender);}
Although I would probably decorate the Enum:
public enum SearchResults
{
[Display(Name="_CustomerPrefixInfo")]
CustomerFound
}
And then there is no switch statement, you just grab the enum value display attribute's name value.
Make sure you are including jQuery and jquery.unobtrusive-ajax.js (or jquery.unobtrusive-ajax.min.js) at the top of your page you want to perform the Ajax on.
Like this:
<script src="~/scripts/jquery-1.x.x.js" />
<script src="~/scripts/jquery.unobtrusive-ajax.js" />
...
The rest of your page down here
...
This allows the Partial's postback to be rendered in-page, rather than re-directing you each time.
If you haven't got the jQuery files, or are unsure how to include them; have a read here: http://www.c-sharpcorner.com/UploadFile/4fcb5a/update-a-div-and-partial-view-using-ajax-beginform-on-form-s/ - it's very useful.
Ensure that Unobtrusive JS is enabled, in your web.config file, also. They look like this in the <appsettings> node:
<add key="ClientValidationEnabled" value="true"/>
<add key="UnobtrusiveJavaScriptEnabled" value="true"/>
If you are using validation - which I doubt you are for a search; but stay with me - you may also want to include jquery.validate.js and jquery.validate.unobtrusive.js, too. This can allow client-side validation (where possible) making the UX a lot smoother.
NOTE: There are other ways of doing the Ajax request; such as creating it in your own JS script - this example is the easiest using an already-available and much-loved library :)
Hope this helps!
EDIT
The switch on which Partial view to return can then be done solely from inside your Controller. You can remove the logic from within the page view.
The Ajax will then take care of replacing the content of the div's ID you gave to the Ajax.BeginForm helper.
You could either replace the search form entirely - by using its ID, or, better still - place an empty <div> container within the page, ready to display the results in:
<div id="searchResults"></div>
The Ajax will then return the Partial view and put the content inside your searchResults div.
EDIT 2
I noticed in your Ajax.BeginForm's AjaxOptions object you are missing HttpMethod = "POST".
The AjaxOptions should look something like this:
new AjaxOptions {
HttpMethod = "POST",
UpdateTargetId = "searchResults",
InsertionMode = InsertionMode.Replace
}
Using MVC, in the GET function in the controller I am creating the VM and passing it to the view.
[Themed]
public ActionResult OrderManufacturedProducts(int id)
{
QBProductRecord QBproduct = _qbproductService.GetById(id);
OrderManufacturedProductsVM model = new OrderManufacturedProductsVM(QBproduct);
return View(model);
}
Then The View:
#model System.ViewModels.OrderManufacturedProductsVM
#{
Script.Require("ShapesBase");
Layout.Title = T("Manufactured Orders").ToString();
}
#using (Html.BeginFormAntiForgeryPost())
{
<fieldset>
<table class="items" summary="#T("This is a table of the manufactured products to be ordered")">
<colgroup>
<col id="Col1" />
<col id="Col2" />
<col id="Col3" />
</colgroup>
<thead>
<tr>
<th scope="col"> ↓</th>
<th scope="col">#T("Name")</th>
<th scope="col">#T("Description")</th>
</tr>
</thead>
<tbody>
<tr>
<td>#Model.QBProduct.ProductName</td>
<td>#Model.QBProduct.StockLevel</td>
<td><input id="Order" name="NoToOrder" type="text" value="0" onchange=""/></td>
</tr>
</tbody>
</table>
<div class="align right"><button type="submit" name="command" value="Save">Order</button></div>
</fieldset>
}
So the user enters a order no. in the input field and clicks submit which returns to the post.
[HttpPost, ActionName("OrderManufacturedProducts")]
public ActionResult OrderManufacturedProductsPOST(int id, int NoToOrder)
{
// OrderManufacturedProductsVM model = new OrderManufacturedProductsVM(QBproduct);
// return View(model);
return Index();
}
I want to return to the Index page, but it is telling me
Server Error in '/OrchardLocal' Application.
The model item passed into the dictionary is of type 'System.ViewModels.ManufacturedProductsVM', but this dictionary requires a model item of type 'System.ViewModels.OrderManufacturedProductsVM'.
So do I need to use the same VM in my post? all I want to do is reload the index page after the update is made.
NOTE: the update on the record is working fine, I just cant get the correct page to display after.
In your OrderManufacturedProductsPOST action, you should redirect to the action you want to return. Like so:
[HttpPost, ActionName("OrderManufacturedProducts")]
public ActionResult OrderManufacturedProductsPOST(int id, int NoToOrder)
{
// OrderManufacturedProductsVM model = new OrderManufacturedProductsVM(QBproduct);
// return View(model);
return RedirectToAction("Index");
}
I'm working on asp.net mvc 3 application. I'm implementing a razor view which have tow main functions - to build/display form based on a data from a data base and to show images related to this form in a custom (made by me) image gallery that allows upload and delete of image.
So generally this is my view with both forms for visualizing the form and showing and uploading image(s) :
#model List<DataAccess.MCS_DocumentFields>
#{
ViewBag.Title = "Документ";
}
<div id="alabala">
<div id="drawForm">
#using (Html.BeginForm("UpdateDocument", "Forms", FormMethod.Post))
{
<table border="1" id="drawDocument">
<colgroup>
<col span="1" style="width: 10%;" />
<col span="1" style="width: 40%;" />
<col span="1" style="width: 25%;" />
<col span="1" style="width: 25%;" />
</colgroup>
#Html.Partial("_PartialHeader", Model)
#Html.Partial("_PartialDrawing", Model)
#Html.Partial("_PartialBody", Model)
#Html.Partial("_PartialFooter", Model)
</table>
if (ViewBag.Status == 1)
{
<button type="submit" id="submitDocument">Запази</button>
<button style="float:right;" id="finalizeDocument">Приключи</button>
}
else
{
#Html.ActionLink("Назад", "Index")
}
}
</div>
<div id="imageContainer">
<div id="imageGallery" style="overflow: scroll">
<img src="file:\\..." alt="docImg" style="width: 190px; height: auto"/>
#Ajax.ActionLink("Delete", "DeletePicture", new { documentID = Model[0].Id },
new AjaxOptions
{
Confirm = "Are you sure?",
OnComplete = "$('#blah').attr('src', '#').attr('style', 'display:none;'); $('#Image1').attr('src', '#').attr('style', 'display:none;'); $('#DelPic').attr('style', 'display:none;');"
})
<img src="file:\\..." alt="docImg" style="width: 190px; height: auto"/>
#Ajax.ActionLink("Delete", "DeletePicture", new { documentID = Model[0].Id },
new AjaxOptions
{
Confirm = "Are you sure?",
OnComplete = "$('#blah').attr('src', '#').attr('style', 'display:none;'); $('#Image1').attr('src', '#').attr('style', 'display:none;'); $('#DelPic').attr('style', 'display:none;');"
})
</div>
#using (Html.BeginForm("Upload", "Forms", FormMethod.Post))
{
<input name=#Model[0].DocumentId type="hidden" />
<input type="file" name="datafile" id="file" onchange="readURL(this);" />
<input type="button" name="Button" value="Upload" id="UploadButton" onclick="fileUpload(this.form,'/forms/upload','upload'); return false;"/>
<div id="upload" style="display: inline-block;">
<img id="blah" src="#" alt="your image" style="display:none;"/>
</div>
}
</div>
</div>
The first form is where I show the data for a current form/document and because they are editable I have submit button. I need the second form to submit the selected picture to my controller and there to perform the business logic.
So once a picture is selected and Upload button is clicked I get to my controller :
public ActionResult Upload(FormCollection collection)
{
WebImage UploadImage = WebImage.GetImageFromRequest();
long documentID;
string finalImageName = null;
if (!long.TryParse(collection.AllKeys[0], out documentID))
//More code...
Where I have the image and the id of the document that it belongs to and what I need is to perform some checks/validations and finally to coy the selected image to dedicated directory and save the name to the data base.
The problem is that I have all the logic written except the one that will show the correct messages for the different outputs like :
if (imagePath.Length > 247)
{
//TODO message that the path is too long
//TODO this return View() is temp, replace with something suitable
return View();
}
//...
System.IO.File.Copy(UploadImage.FileName, imagePath);
}
catch (Exception ex)
{
//TODO copy failed return message
return View();
}
//...
These are all different outputs from the execution of the same method and in the main view I want to show a proper message for each one of them. What I'm not sure is if I still have an option to save my work and still implement the message logic? Thinking now it seems that if I was using Ajax in some form it would be a lot easier now but I'm not. The only think I can think of know is creating ViewBag property, and returning it with the model to the view where to check the different properties and show some message if necessary based on that, but this means a lot of additional logic in my view, resending data from the database that I already have shown in my view and a lot of double work said in short, something that I consider as a bad programming, but maybe I got myself into this. So what is the best course of action from here on. Is it best to just remove my code and search for a way to do this with AJAX?
You can't upload a file using AJAX - you'd need some sort of 3rd party workaround. You need to return the original view from the Upload() method, with the appropriate model, and also a flag within the ViewBag somewhere to display the message, e.g.
public ActionResult Upload(UpdateDocumentModel model) {
...
if (imagePath.Length > 247) {
model.ErrorMessage = Errors.Over247;
return View("UpdateDocument", model);
}
...
return RedirectToAction("UploadOk");
}
I've changed your FormCollection to a strongly-typed model for ease of reading, plus that's what MVC.net is there for. The Errors.Over247 could be a string resource somewhere in your project, or a boolean flag which the View then reads to show a certain piece of HTML.
just use TempData instead of ViewBag
TempData["ErorrMessegge"] = "SomeMessage to view";
#TempData["ErorrMessegge"]
You can simply use TempData[] in order to pass parameter from Controller to View like below:
Controller:
[HttpPost]
public ActionResult Add(Applicant applicant)
{
repository.SaveApplicant(applicant);
TempData["message"] = "The applicant has been saved succesfully.";
return View(applicant);
}
View:
#if (TempData["message"] != null)
{
<div>#TempData["message"]</div>
}