I have a layout page which is used by my MVC site. It has no models or any related controller methods.
But now I want to add a "Search" box at the top. I can't add a normal "Form" because it get's over ridden, or overrides any other forms on the content pages. So I was thinking I might have to do it with Javascript... call a javascript function that then sends the query to a controller, and then moves the user to the result screen.
Is this the right way to do it? Or can I somehow use a normal controller method call from my layout page?
Let's assume you have these models:
public class SearchModel
{
public string SearchTerm { get; set; }
}
public class LoginModel
{
public string UserName { get; set; }
public string Password { get; set; }
}
And that you also had these controllers:
public class HomeController : Controller
{
public ActionResult Search(SearchModel model)
{
return View();
}
}
public class AccountController : Controller
{
public ActionResult Login(LoginModel model)
{
return View();
}
}
You could use something like this as a layout view:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>#ViewBag.Title</title>
</head>
<body>
#using(Html.BeginForm("search", "home", FormMethod.Post, new {}))
{
<input type="search" name="SearchTerm" placeholder="Find..." />
<button type="submit">GO</button>
}
#if(!Request.IsAuthenticated)
{
using(Html.BeginForm("login", "account", FormMethod.Post, new {}))
{
<input type="text" name="UserName" placeholder="..." />
<input type="password" name="Password" placeholder="..." />
<button type="submit">GO</button>
}
}
#RenderBody()
</body>
</html>
The model binder is smart enough to match up request parameters to the correct model properties, even if you don't use the #Html helpers to create your HTML controls. The problem you'll run into is what to do with, say, invalid login attempts. Seems like the typical workflow is to have a normal login action - that lets you dump the user to a proper login page if they enter invalid credentials in, say, a navbar login form.
Another option is to embed the search and login forms as child actions:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>#ViewBag.Title</title>
</head>
<body>
#Html.Action("search", "home")
#Html.Action("login", "account")
#RenderBody()
</body>
</html>
If you go that route, you would modify the controller to return partial views. Here's one implementation of an AccountController:
public class AccountController : Controller
{
[HttpGet, AllowAnonymous]
public ActionResult Login(string returnUrl = "")
{
return View();
}
[HttpPost, AllowAnonymous]
public ActionResult Login(LoginModel model)
{
if(ModelState.IsValid)
{
/* valid user credentials */
return RedirectToAction("index", "home");
}
return View(model);
}
[AllowAnonymous, ChildActionOnly]
public ActionResult NavbarLogin()
{
return PartialView();
}
}
Notice the last action method; marking it as ChildActionOnly prevents the action from being directly requested by the browser. The view for that method might look like this:
#model Your.Fully.Qualified.Namespace.Models.LoginModel
#using(Html.BeginForm("login", "account", FormMethod.Post, new { }))
{
#Html.LabelFor(m => m.UserName)
#Html.TextBoxFor(m => m.UserName)
#Html.ValidationMessageFor(m => m.UserName)
#Html.LabelFor(m => m.Password)
#Html.PasswordFor(m => m.Password)
#Html.ValidationMessageFor(m => m.Password)
<button type="submit">Login</button>
}
The advantage of the strongly-typed helpers is that MVC will create the validation data- attributes that the Unobtrusive Validation library leverages; include the jqueryval bundle on the layout and any view with strongly-typed helpers gets client-side validation automatically.
In this example, the "navbar login" posts to the normal login action, so invalid credentials will result in the user being shown the login page, rather than the page they were originally viewing.
Related
I have a simple cshtml page in MVC 4 as following:
#model MvcApplication2.ViewModels.UserViewModel
<html>
<head>
<meta name="viewport" content="width=device-width" />
<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>
</head>
<body>
<div>
#using (Html.BeginForm("Add", "User", FormMethod.Post))
{
#Html.ValidationMessageFor(m => m.FirstName)
#Html.TextBoxFor(m => m.Ime)
<br />
#Html.ValidationMessageFor(m => m.LastName)
#Html.TextBoxFor(m => m.LastName)
<br />
<input id="btnSubmit" type="submit" value="Add user" />
}
</div>
</body>
</html>
And this is my UserViewModel class:
public class UserViewModel
{
[Required(ErrorMessage = "This field is required.")]
public string FirstName;
[Required(ErrorMessage = "This field is required.")]
public string LastName;
}
And this is the action "add" in the controller:
[HttpPost]
public ActionResult Add(User s)
{
if(ModelState.IsValid)
{
Connection.dm.User.Add(s);
Connection.dm.SaveChanges();
}
return RedirectToAction("Index");
}
The issue here is that I get no error messages at all if the user hasn't entered something in the 2 textbox fields... What could it be?
in reference to using try.. catch.. blocks, you could do something like this:
[HttpPost]
public ActionResult Add(User s)
{
if(ModelState.IsValid)
{
try{
Connection.dm.User.Add(s);
Connection.dm.SaveChanges();
}
catch(NullReferenceException)
{
ModelState.AddModelError("Ime", "Please enter something"); // for the first name validation
return View(s);
}
return RedirectToAction("Index");
}
or you could use some conditional logic like this:
[HttpPost]
public ActionResult Add(User s)
{
if(ModelState.IsValid)
{
if(string.IsNullOrWhiteSpace(s.Ime)
{
ModelState.AddModelError("Ime", "Please enter something");
return View(s);
}
else
{
Connection.dm.User.Add(s);
Connection.dm.SaveChanges();
}
}
return RedirectToAction("Index");
}
I want to load an external Json data File which contain the city name of a country.I want to show it in the search option in Index.cshtml.My Json file look like this-
CityName[
{
"City": "Flensburg"
},
{
"City": "Kiel"
},
{
"City": "Lübeck"
},
{
"City": "Neumünster"
}
]
Now I created City class inside the Model to get the name from this object.
public class City
{
public string City { get; set; }
}
My Controller Class look like this-
public class HomeController : Controller
{
public ActionResult Search(string name)
{
return View();
}
}
Now for the view I used Javascript and created one search box with button like this-
<div class="search-form">
<form action="index.html" method="get">
<div class="input-group">
<input type="text" placeholder="Enter Location Name" name="search" class="form-control input-lg">
<div class="input-group-btn">
<button class="btn btn-lg btn-primary" type="submit">
Search
</button>
</div>
</div>
</form>
</div>
Now I want to set the city name in the search option. But as I am very new in handling MVC, I am not sure how to proceed with it.
I think you should provide a dropdown with the available cities instead of a text input. As an alternative, use an autocompleter. Here is the solution with a dropdown.
First, you should create two Actions, one to display the page initially [HttpGet] and one to handle the post of the form [HttpPost].
The GET action should return a strongly typed view, the ViewModel contains your search parameters.
The View sends the filled out ViewModel to the post action.
public class SearchViewModel {
public City SelectedCity {get; set;}
// to be read from JSON
public City[] AvailableCities {get; set;}
// generate SelectListItems to be used with DropDownListFor()
public IEnumerable<SelectListItem> CityOptions { get {
foreach (var city in AvailableCities) {
yield return new SelectListItem {
Value = city.City,
Text = city.City,
Selected = city == SelectedCity
};
}
}}
}
// GET action
[HttpGet]
public ActionResult Search() {
var vm = new SearchViewModel();
vm.AvailableCities = // load from JSON
return View("Search", vm);
}
// Razor View <ControllerName>\Search.cshtml
#model SearchViewModel
#using (Html.BeginForm("Search", "<ControllerName>", FormMethod.Post)) {
#* render Cities dropdown; bind selected value to SelectedCity *#
#Html.DropDownListFor(m => m.SelectedCity, Model.CityOptions)
<button class="btn btn-lg btn-primary" type="submit">Search</button>
}
// POST Action
[HttpPost]
public ActionResult Search(SearchViewModel vm) {
var selected = vm.SelectedCity;
// search ...
}
"ControllerName" should be the name of the controller implementing the actions, e.g. "Home".
How can I change the simple code below so that the simple AngularJS code below can successfully retrieve values from the simple Spring MVC REST Controller below? At the moment, nothing happens when I click on the button in the html form.
Here is index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Find Book By ISBN</title>
</head>
<body ng-app="bookApp">
<div ng-controller="bookController">
<table>
<tr>
<td width=200>
ISBN:<input type="text" ng-model="book.isbn" />
<br>
<button ng-click="findBook()">Find Book</button>
</td>
<td>
ISBN: <span ng-bind="book.isbn"></span>
<br/>
Title: <span ng-bind="book.title"></span>
<br/>
Author: <span ng-bind="book.author"></span>
</td>
</tr>
</table>
</div>
</body>
</html>
Here is bookController.js, which is located in the same directory as index.html for testing:
// create angular app
var bookApp = angular.module('bookApp', []);
// create angular controller
bookApp.controller('bookController', ['$scope', '$http', function($scope, $http) {
var bookId = 1;
$scope.findBook = function() {
$http.post('/findbook/' + bookId, $scope.book);
};
}]);
Here is the Book.java model class:
public class Book {
private String isbn;
private String title;
private String author;
public void setIsbn(String id){isbn=id;}
public String getIsbn(){return isbn;}
public void setTitle(String t){title=t;}
public String getTitle(){return title;}
public void setAuthor(String a){author=a;}
public String getAuthor(){return author;}
}
And here is the Spring MVC REST controller:
#RestController
#RequestMapping("/api")
public class BookRestController {
#RequestMapping(value = "/findbook/{bookId}", method = RequestMethod.POST)
public #ResponseBody Book create(#PathVariable("bookId") String bookId, #RequestBody Book book) {
System.out.println("The Server Heard The Request!");
Book newBook = new Book();
if (book.getIsbn().equals("123a")){
newBook.setAuthor("J.K. Rowling");
newBook.setTitle("Harry Potter");
newBook.setIsbn(bookId);
}
else if(book.getIsbn().equals("b321")) {
newBook.setAuthor("Stan Lee");
newBook.setTitle("Spiderman");
newBook.setIsbn(bookId);
}
else {
newBook.setAuthor("Author not specified.");
newBook.setTitle("Title note specified.");
newBook.setIsbn(bookId);
}
return newBook;
}
}
Change $scope.findBook as follows,
$scope.findBook = function() {
$http.get('/findbook/' + bookId).then(function(response) {
$scope.book = response.data;
});
};
Once the promise has been resolved, it will update $scope.book and that should reflect in your HTML.
I am not sure in your code $http.get('/findbook/' + bookId, $scope.book);, what does $scope.book do?
Not sure, but if i remember, Spring MVC doesn't support Request Body on a GET request.
When mvc application is queried with controller name alone in the url without specifying action, the page is rendered but ajax/scripts are not working, whereas the same page when queried with action in the url, is working as expected.
Not working url: http://localhost:port/Search --> Page rendering is fine but scripts are not working - Search results are not showing up
Working url: http://localhost:port/Search/Index --> Page and scripts are working as expected - Search results are showing up
C#:
public class SearchController : Controller
{
private readonly List<string> _cars;
public SearchController()
{
_cars = new List<string>{"Corolla","Camry","Civic","Land Rover","Range Rover","Polo"};
}
public ActionResult Index()
{
return View();
}
public async Task<JsonResult> GetMatchingResults(string filter)
{
var results = await Task.Run(() => GetSearchResults(filter));
return new JsonResult() { Data = results,JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
private List<string> GetSearchResults(string filter)
{
var results = _cars.Where(car => car.Contains(filter));
return results.ToList();
}
}
HTML:
<html>
<head>
#using System.Web.Optimization
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/bootstrap")
<meta name="viewport" content="width=device-width" />
<script src="~/Scripts/ApplicationScripts/SearchViewJS.js" type="text/javascript"></script>
<title>SearchView</title>
</head>
<body>
<div>
<input class="searchText" type="search" />
</div>
<div>
<input class="searchResults" type="text" />
</div>
</body>
</html>
JS:
$(document).ready(function () {
$(".searchText").on('input', function (event) {
var filter = $(event.currentTarget).val();
search(filter).then(display);
});
function search(filter) {
return $.getJSON('GetMatchingResults/', { 'filter': filter });
}
function display(result) {
$(".searchResults").val(result);
}
})
It is because of the context of
$.getJSON('GetMatchingResults/', { 'filter': filter });
In the first case that will be trying to hit /GetMatchingResults the second tries to hit /search/GetMatchingResults. A fix could be to use
$.getJSON('/search/GetMatchingResults/', { 'filter': filter });
Or even better would be to generate the path from a HTML helper that will route correctly if you update your routing rules. This would look something like
$.getJSON('#Url.Action("GetMatchingResults", "Search")', { 'filter': filter });
My JavaScript will not run. I've been struggling with this for a while (2 days) Here's my current setup:
Created a new MVC 4 project. I intend to try out some AJAX, so named it AjaxTest.
Added a model (Vehicle.cs) consisting of three classes OdometerInfo, VehicleMinInfo, and Vehicle. Here they are:
public class OdometerInfo
{
public virtual string VIN { get; set; }
public virtual int Odometer { get; set; }
}
public class VehicleMinInfo : OdometerInfo
{
public virtual Nullable<int> VehicleYear { get; set; }
public virtual string Make { get; set; }
public virtual string Model { get; set; }
}
public class Vehicle : VehicleMinInfo
{
// default constructor
public Vehicle()
{
VIN = "MyFakeVin";
Odometer = 0;
VehicleYear = 2012;
Make = "Porsche";
Model = "911";
}
public override string VIN { get; set; }
public override int Odometer { get; set; }
public override Nullable<int> VehicleYear { get; set; }
public override string Make { get; set; }
public override string Model { get; set; }
// other fields
}
Then I replaced the contents of the template Index.cshtml with:
#model AjaxTest.Models.VehicleMinInfo
#{
ViewBag.Title = "Index";
}
<h2>Enter your odometer reading</h2>
#using (Html.BeginForm("Odometer", "Home", FormMethod.Post))
{
<h4>For the following vehicle.</h4>
#Html.DisplayFor(model => model.VIN) <br />
#Html.DisplayFor(model => model.VehicleYear) <br />
#Html.DisplayFor(model => model.Make) <br />
#Html.DisplayFor(model => model.Model) <br />
<h1>Enter Odometer</h1>
#Html.DisplayNameFor(model => model.Odometer)
#Html.EditorFor(model => model.Odometer)
#Html.HiddenFor(model => model.VIN);
<input type="submit" value="Odometer reading is correct" id="OdometerForm" />
}
Then I made a strongly typed view (Odometer.cshtml):
#model AjaxTest.Models.OdometerInfo
#{
ViewBag.Title = "Odometer";
}
<h2>Odometer</h2>
Your odometer has been entered. It is
#Html.DisplayFor(model => model.Odometer)
. (
#Html.DisplayFor(model => model.VIN)
)
And added to the controller:
public ActionResult Index()
{
VehicleMinInfo OdomObj = new Vehicle();
return View(OdomObj);
}
[HttpPost]
public ActionResult Odometer(OdometerInfo oi)
{
return View(oi);
}
All of that works. I can fill in an odometer reading and both the odometer and the VIN are passed back to the controller and displayed on the Odometer page. Now, it's time to start adding some JavaScript. So I created OdometerList.js with the eventual goal of passing back a list of odometer readings instead of just one, and in it I placed:
$("#OdometerForm").click(function () {
alert("Hello world!");
});
window.onload = function () {
if (window.jQuery) {
// jQuery is loaded
alert("Yeah!");
} else {
// jQuery is not loaded
alert("Doesn't Work");
}
}
$(document).ready(function () {
alert("!!!");
});
Then I added in _Layout.cshtml
#Scripts.Render("~/Scripts/jquery-1.3.2.min.js")
#Scripts.Render("~/Scripts/OdometerList.js")
And I double checked Web.config to be sure compilation debug was true:
<system.web>
<compilation debug="true" targetFramework="4.5" />
...
None of my alerts are triggered, not one. Is my setup wrong? I moved my JavaScript from OdometerList.js to the bottom of Odometer.cshtml and put it all between script tags, but there was no change. I am new to JavaScript, so have I made a mistake there?
This is really stumping me. Any help you can give will be much appreciated!
The version of jQuery had been updated. Once I put the correct version number in, I was able to use #Scripts.Render("~/Scripts/jquery-1.8.2.min.js") and everything works!