MVC controller multiple views and routing issue - c#

At the moment we have view name 'Self Deposit' and a controller to back it.
The view is a stand alone page that built with partial views:
Example
Folder name: SelfDeposit
Main View file: _LayoutSelfDeposit.cshtml
Partial Views: Register.cshtml, Pending.cshtml etc.
This is the main view:
<html dir="#(System.Globalization.CultureInfo.CurrentUICulture.TextInfo.IsRightToLeft ? "rtl" : "ltr")">
<head>
<meta name="viewport" content="width=device-width" />
<title>#ViewBag.Title</title>
#Styles.Render("~/Content/SelfDeposit_css")
#Scripts.Render("~/bundles/scripts")
#Scripts.Render("~/bundles/bootstrap")
#Scripts.Render("~/bundles/jqueryval")
#if (System.Globalization.CultureInfo.CurrentUICulture.TextInfo.IsRightToLeft)
{
#Styles.Render("~/Content/BootstapRtl_css")
}
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,700|PT+Sans" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-12 text-center">
</div>
</div>
</div>
#RenderBody()
<div class="container text-center">
<img src="~/Content/SelfDeposit/footer-icons.png" class="img-responsive footer-icons" />
</div>
#Html.Partial("ThirdPartyPixels/GoogleTagManager")
</body>
</html>
This is the controller that loads the start of it:
private readonly ForexDbContext _Db = DbLoader.GetDb();
// GET: SelfDeposit
public ActionResult Index()
{
return RedirectToAction(nameof(this.Register));
}
public async Task<ActionResult> Register()
{
ViewBag.CountryId = await GetCountiesAsync();
var model = new SelfDepositRegistrationViewModel { };
if (TempData[Main.LEAD_REG_DETAILS_FOR_OPEN_ACCOUNT] != null && TempData[Main.LEAD_REG_DETAILS_FOR_OPEN_ACCOUNT] is LeadRegistered)
{
var leadRegistered = TempData[Main.LEAD_REG_DETAILS_FOR_OPEN_ACCOUNT] as LeadRegistered;
ViewBag.LeadRegisteredDetails = leadRegistered;
model.FirstName = leadRegistered.FirstName;
model.LastName = leadRegistered.LastName;
model.Email = leadRegistered.Email;
model.PhoneNumber = leadRegistered.Phone;
model.PhoneCountryCode = leadRegistered.PhoneCountry;
}
return View(model);
}
What i am trying to achieve is to have multiple Views each one with different layout and css but keep the controller the same and not copy it each time i am adding a view.
I have tried the following: Adding a folder under the main folder:
SelfDeposit-->Layout1-->_LayoutSelfDeposit.cshtml
But it didn't work since i couldn't figure the routing problem.
Does anyone have ideas?
Thanks

To return a different view, you can specify the name of the view you want to return and model as follows:
return View("ViewName", yourModel);
To get the absolute path of the view, use:
return View("~/Views/FolderName/ViewName.cshtml");
Or you can make a partial view and can return like:
return PartialView("PartialViewName", Model);

Related

How to use MVC Model binding with an Angular ng-bind attribute

In my view I have a tag with an ng-bind attribute that is showing the correct boolean value:
<span id="ShowFlag" name="ShowFlag" ng-bind="session.view.showFlag"></span>
When the form is posted on the server side I would like to bind this to a property on the relevant model.
public bool ShowFlag { get; set; }
However, this is always returning false, whereas the value shown in Span tag is showing correctly as true on the page. Is there something obvious I'm missing here?
I think you're something you're missing about how AngularJs binding works. if you want to get a value from the server into an angular model you can use Razor to get that data into JavaScript (the best place is in your Angular controller.)
Here is a quick sample I put together.
This is code from the MVC Controller. In this example we are using Model data and ViewBag data.
public ActionResult Index()
{
dynamic model = new ExpandoObject();
model.ShowFlag = "True";
ViewBag.ShowFlag = "ViewBag True";
return View(model);
}
This is what the view looks like including reference so Angular, JQuery and the code for the AngularJs app and controller:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Demo</title>
</head>
<body>
<div>
<h2>Sample For Stack Overflow</h2>
<div ng-app="glennapp">
<div ng-controller="testController">
<input type="text" ng-model="showFlag" />
<input type="text" ng-model="showFlag2" />
<div>
<span ng-bind="showFlag" ></span>
<span ng-bind="showFlag2" ></span>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="//code.jquery.com/jquery-1.11.3.min.js"></script>
<script type="text/javascript" src="//code.angularjs.org/1.4.8/angular.min.js"></script>
<script type="text/javascript">
var mainApp = angular.module('glennapp', ['glennControllers']);
var glennControllers = angular.module('glennControllers', []);
glennControllers.controller('testController', ['$scope', function ($scope) {
$scope.showFlag = '#ViewBag.ShowFlag';
$scope.showFlag2 = '#Model.ShowFlag';
}]);
</script>
</body>
</html>
Another option would be to create an MVC action that returns JsonResult and then write some JavaScript to make an Ajax call and retrieve the data.
When posting a form only input and select tag values are passed to the server
in you case ShowFlag is a span, so you need to make it an input:
<input type="checkbox" id="ShowFlag" name="ShowFlag" ng-bind="session.view.showFlag"/>
If you are posting to server with ajax, make sure that you serialize your model properly:
for example for the following action:
public ActionResult (FlagsConatiner container)
{
//
}
public class FlagsConatiner
{
public bool ShowFlag { get; set; }
}
Serialized model should look like this:
{
"ShowFlag":"true"
}
As pointed out above, you must use an input for the binding to be successful. I used the following which is now working:
<input type="hidden" id="ShowFlag" name="ShowFlag" ng-value="session.view.showFlag">

Cannot perform runtime binding on a null reference. ViewBag.Title is null

I want to provide a simple general functionality to show a message at the top of a page whenever I need to inform a user that operation is successful.
My solution would be to put an object into ViewBag.Info that contains details about a message, including bool public IsMessage;and read it in _Layout.cshtml where I have an extra hidden div.
The way I am trying to do it in _Layout.cshtml is I put what's in #(ViewBag.Info.IsMessage (false/true) into a hidden field and read it by JavaScript. If the field contains "true", javascript will invoke a function ShowEvent().
For some reason, if I add the line #(ViewBag.Info.IsMessage.ToString().ToLower()) VS2010 complains about ViewBag.Title in _Layout.cshtml.
"Cannot perform runtime binding on a null reference"
The code of _Layout.cshtml is simple:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<link rel="Stylesheet" type="text/css" href="#Url.Content("~/Content/StyleSheet.css")" />
<script src="~/Scripts/jquery-2.1.1.min.js" type="text/javascript"></script>
<title>#ViewBag.Title</title>
</head>
<body>
<input type="hidden" id="is-event" name="is-event" value="#(ViewBag.Info.IsMessage.ToString().ToLower())"/>
<div class="recent-events"></div>
<div>
#if (IsSectionDefined("loginfo"))
{
#RenderSection("loginfo", false)
}
#RenderBody()
#if (IsSectionDefined("Home"))
{
#RenderSection("Home", false)
}
</div>
</body>
</html>
And the code of the View Index.cshtml is:
#model TestProject.ViewModels.Account.UserData
#{
ViewBag.Title = "Index";
string identityName = HttpContext.Current.User.Identity.Name;
string userrole = Model.UserRoles.FirstOrDefault();
}
<h2>Index</h2>
<br />
As you can see ViewBag.Title is defined.
EDIT.
The code of the controller:
public ActionResult Index()
{
// ...
InfoModel infoModel = new InfoModel()
{
IsMessage = true, Duration = 3000, Message = "Logging in successfull", BackgroundColor = "#bbffbb"
};
ViewBag.Info = infoModel;
ViewBag.Title = string.Empty;
return View(userdata);
}
You can get this issue if you attempt to reference a Model from a View which isn't strongly typed. For instance, typing
#Model.ID
somewhere in the page will attempt to bind to the ID property of the ViewModel - if you haven't defined the model type at the top of the View page with
#model MyModel
then it will bomb out with this obscure error message.
Make sure that you have defined the ViewBag.Title in your controller action. Right now you have defined it in your Index.cshtml view but the Layout is rendered first so by that time the value is not yet set.

knockoutjs mvc button action change per viewModel

I am currently using knockoutjs with one of my MVC applications.
The Layout template looks like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>#ViewBag.Title</title>
#Styles.Render("~/Content/css")
#Scripts.Render("~/bundles/modernizr")
</head>
<body>
<div class="container-fluid head-content">
<div class="row">
<div class="col-xs-6">
<img class="img-responsive" src="~/Images/logo.jpg" />
</div>
<div class="col-xs-3">
<a class="block" href="#" style="display: none" data-bind="visible: showBack, click: goBack">
<div class="block-text">
<h4>Back</h4>
</div>
</a>
</div>
<div class="col-xs-3">
<a class="block" href="#" style="display: none" data-bind="visible: showHome, click: navigateToHome">
<div class="block-text">
<h4>Home</h4>
</div>
</a>
</div>
</div>
</div>
<div class="container-fluid body-content">
#RenderBody()
</div>
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/bootstrap")
#RenderSection("scripts", required: false)
</body>
</html>
and my Index partial looks like this:
#Html.Partial("_Login")
#Html.Partial("_Home")
#Html.Partial("_CutLengths")
#Html.Partial("_MoveStock")
#section scripts {
#Scripts.Render("~/bundles/knockout")
#Scripts.Render("~/bundles/app")
}
My problem is that depending on which page I am on, I would like to use the back button to go to another page. For example, if I am on cutLengths I would want the back button to take me home.
My app.viewmodel.js has a method which looks like this:
// Other operations
self.addViewModel = function (options) {
var viewItem = {},
navigator;
// Add view to AppViewModel.Views enum (for example, app.Views.Home).
self.Views[options.name] = viewItem;
// Add binding member to AppViewModel (for example, app.home);
self[options.bindingMemberName] = ko.computed(function () {
if (self.view() !== viewItem) {
return null;
}
return new options.factory(self, dataModel);
});
if (typeof (options.navigatorFactory) !== "undefined") {
navigator = options.navigatorFactory(self, dataModel);
} else {
navigator = function () {
self.view(viewItem);
};
}
// Add navigation member to AppViewModel (for example, app.NavigateToHome());
self["navigateTo" + options.name] = navigator;
};
What I would like to do is pass a string from the ViewModel I am currently viewing which when the back button is pressed will know to direct me to the right ViewModel.
Is it possible to do this?
I hope I have explained it well, if I haven't please ask and I will try harder :D
You can use either ViewData or ViewBag for passing data from the controller to view. So one option is to just add a few dynamic properties to ViewBag for current view model and prior view model.
ViewData is a dictionary of objects that are stored and retrieved using strings as keys.
ViewBag uses the dynamic feature that was introduced into C# 4.It allows an object to have properties dynamically added to it. I would use this for passing your view model state around.
Neither provide compile time checking, which is the beauty of them, you can add anything you want. With that said it’s always good practice to use strongly typed view models over ViewBag and ViewData.
If you'd rather put something in your view model instead of adding properties to ViewBag, than just add another property in each view model called PreviousViewModel and populate it any time you use the model.
Examples using ViewBag or ViewData
ViewData["LastViewModel"] = "CutLengths";
ViewBag.LastViewModel = "CutLengths";
Access your ViewBag in the Views is no problem, they have global scope. ViewBag is like a global variable that you can attach anything to-- so I'd use them judiciously-- maybe some type of a singleton application manager would be a better design.
Hope this helps
I have solved this now. First I changed my HTML
<div class="col-xs-3">
<a class="block btn-back" href="#" data-bind="visible: showBack, click: goBack"></a>
</div>
<div class="col-xs-3">
<a class="block btn-home" href="#/home" data-bind="visible: showHome"></a>
</div>
Then I edited my app.viewmodel.js file and added these
// Data
self.back = ko.observable(null);
// UI state
self.showBack = ko.observable(true);
self.showHome = ko.observable(true);
self.goBack = function () {
if (self.back()) {
window.location.href = self.back();
} else {
window.history.back();
}
self.back(null); // Reset
};
self.setBackUrl = function (url) {
self.back(url);
}
Then on my addViewModel navigate function, I added this:
if (typeof (options.navigatorFactory) !== "undefined") {
navigator = options.navigatorFactory(self, dataModel);
} else {
navigator = function () { // This is our navigator function which sets the current view
self.showBack(true);
self.showHome(true);
self.error(null); // Reset errors
self.view(viewItem);
};
}
And then in my other view models, I just make a call to setBackUrl like this:
app.setBackUrl("#/cut-lengths");
And if I want to hide my buttons, that is easy too. I just create a navigatorFactory on the viewModel like this:
app.addViewModel({
name: "Home",
bindingMemberName: "home",
factory: HomeViewModel,
navigatorFactory: function (app) {
return function () {
app.showBack(false);
app.showHome(false);
app.error(null);
app.view(app.Views.Home);
}
}
});

Select Items and display in same view. Return results to controller

I am new to mvc and fairly young with Javascript so I apologize for the wrong/missing code. I am trying to make a view where the user has a drop down list and items selected via btnAdd will be dynamically displayed in the same view below the btnAdd button. I am assuming the best way to do this would be with JavaScript. After the user has made there choices they will click the btnckout button and there selections will be returned to the controller. Here is what I have so far. I am a little stuck so any help would be appreciated!
View:
#model OnlineTakeout.Models.ProductView
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<script src="~/Scripts/jquery-2.1.0.min.js"></script>
<title>Index</title>
</head>
<body>
#using (Html.BeginForm()){
<div>
Pick Product:
<br />
#Html.DropDownListFor(m=>m.ProductId, Model.Products)
<br />
<p>
<input type="button" value="AddToOrder" id="btnAdd" />
</p>
</div>
}
<div>
#using (Html.BeginForm()) {
//Added Items would display here after individual btnAdd button presses
<p>
<input type="button" value="CheckOut" id="btnChkOut" />
</p>
}
</div>
</body>
<script>
$(function () {
$("#btnAdd").click(addProduct);
})
$(function () {
$("#btnChkOut").click(saveProducts);
})
var productList = [];
var id = $("#ProductId").val();
// This function would also display these items on view
function addProduct() {
productList.push(id);
};
function saveProducts() {
$.post("/Product/Index/" + productList());
}
}
</script>
Controller:
public class ProductController : Controller
{
//
// GET: /Product/
public ActionResult Index()
{
var model = new ProductView();
var products = new List<Product> { new Product { ProductId = 1, Name = "Product One", Price = 1.00m },
{ new Product { ProductId = 2, Name = "Product Two", Price = 2.00m } }};
model.Products = new SelectList(products, "ProductId", "Name");
return View(model);
}
[HttpPost]
public JsonResult Index(int[] prodList)
{
return Json("Index");
}
The way I usually do this is by using jQuery.
You will need to create an event handler in jQuery for the change event of your drop down list that is supposed to trigger this change. When that fired, post to an action in your controller that is going to bind a partial controller and return the partial view. It is important to have a return type of ActionResult - that will return the HTML back to success method of your post. Then just embed the HTML on the page and you are done.

MVC4 ajax form not working

Hi I'm new to MVC so please bear with me.
I'm trying to do a simple ajax form that just accepts inserts and lets the user know the record has been saved off into a DB.
My problem is two fold.
The data is being inserted into the DB twice
The editors don't get cleared and the message isn't displayed.
I can get this working using straight HTML form posts but wanted to use ajax and then introduce some sort of loading gif or use spin.js.
my code:
_Layout.cshtml
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>#ViewBag.Title</title>
#Styles.Render("~/Content/css")
#Styles.Render("~/Content/kendo")
#Scripts.Render("~/bundles/modernizr")
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/kendo")
#Scripts.Render("~/bundles/jqueryval")
</head>
<body>
<div id="wrapper" style="width:100%; text-align:center">
<img src="~/Content/Header.PNG" alt="MyHeader" />
</div>
#RenderBody()
#RenderSection("scripts", required: false)
</body>
</html>
AboutController
public class AboutController : Controller
{
private readonly IMailRepository repository;
public AboutController(IMailRepository repo)
{
repository = repo;
}
public ActionResult AboutUs()
{
return View();
}
public ActionResult CreateMailing()
{
return View(new MailRequest());
}
[HttpPost]
public PartialViewResult CreateMailing(MailRequest model)
{
if (model == null)
{
return PartialView("_MailingData",new MailRequest());
}
if (ModelState.IsValid)
{
repository.SaveMailRequest(model);
ModelState.Clear();
TempData["message"] = string.Format("{0} has been added to the mailing list.", model.Email);
return PartialView("_MailingData",new MailRequest());
}
else
{
return PartialView("_MailingData",model);
}
}
}
_MailingDate.cshtml
#model MyProj.Models.MailRequest
#Html.EditorForModel()
<br/>
<input type="submit" value="Save"/>
<input type="button" value="Cancel"
onclick="location.href='#Url.Action("AboutUs", "About")' " />
#if (TempData["message"] != null)
{
<div>#TempData["message"]</div>
}
CreateMailing.cshtml
#model MyProj.Models.MailRequest
#{
ViewBag.Title = "Mailing List";
AjaxOptions ajaxOpts = new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "ajaxreplace"
};
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Mailing List Request</title>
</head>
<body>
<div id="ajaxrequest">
#using (Ajax.BeginForm(ajaxOpts))
{
#Html.Partial("_MailingData")
}
</div>
</body>
</html>
----UPDATE
Here is my BundleConfig.cs
public static class BundleConfig
{
// For more information on Bundling, visit http://go.microsoft.com/fwlink/?LinkId=254725
public static void RegisterBundles(BundleCollection bundles)
{
if (bundles == null) return;
// The jQuery bundle
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js"));
//bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
// "~/Scripts/jquery-{version}.js",
// "~/Scripts/jquery-migrate-1.1.1.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
"~/Scripts/jquery-ui-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.unobtrusive*",
"~/Scripts/jquery.validate*"));
// The Kendo JavaScript bundle
bundles.Add(new ScriptBundle("~/bundles/kendo").Include(
"~/Scripts/kendo.all.min.js",
// or kendo.all.min.js if you want to use Kendo UI Web and Kendo UI DataViz
"~/Scripts/kendo.aspnetmvc.min.js"));
bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/site.css"));
bundles.Add(new StyleBundle("~/Content/themes/base/css").Include(
"~/Content/themes/base/jquery.ui.core.css",
"~/Content/themes/base/jquery.ui.resizable.css",
"~/Content/themes/base/jquery.ui.selectable.css",
"~/Content/themes/base/jquery.ui.accordion.css",
"~/Content/themes/base/jquery.ui.autocomplete.css",
"~/Content/themes/base/jquery.ui.button.css",
"~/Content/themes/base/jquery.ui.dialog.css",
"~/Content/themes/base/jquery.ui.slider.css",
"~/Content/themes/base/jquery.ui.tabs.css",
"~/Content/themes/base/jquery.ui.datepicker.css",
"~/Content/themes/base/jquery.ui.progressbar.css",
"~/Content/themes/base/jquery.ui.theme.css"));
// The Kendo CSS bundle
bundles.Add(new StyleBundle("~/Content/kendo").Include(
"~/Content/kendo.common.*",
"~/Content/kendo.uniform.*"));
// Clear all items from the ignore list to allow minified CSS and JavaScript files in debug mode
bundles.IgnoreList.Clear();
// Add back the default ignore list rules sans the ones which affect minified files and debug mode
bundles.IgnoreList.Ignore("*.intellisense.js");
bundles.IgnoreList.Ignore("*-vsdoc.js");
bundles.IgnoreList.Ignore("*.debug.js", OptimizationMode.WhenEnabled);
}
}
I think my DB issue may have something to do with the fact I end up with the following in the html of my page
<script src="/bundles/modernizr"></script>
<script src="/Scripts/jquery-2.0.0.js"></script>
<script src="/Scripts/jquery-2.0.3.js"></script>
<script src="/Scripts/kendo.all.min.js"></script>
<script src="/Scripts/kendo.aspnetmvc.min.js"></script>
<script src="/Scripts/jquery.unobtrusive-ajax.js"></script>
<script src="/Scripts/jquery.unobtrusive-ajax.min.js"></script>
<script src="/Scripts/jquery.validate.js"></script>
<script src="/Scripts/jquery.validate.min.js"></script>
<script src="/Scripts/jquery.validate.unobtrusive.js"></script>
<script src="/Scripts/jquery.validate.unobtrusive.min.js"></script>
I'm guessing I shouldn't have both the full and min js scripts registered but am not sure the best way to prevent this whilst still using bundles
My EFMailRepository
public class EFMailRepository : IMailRepository, IDisposable
{
private EFDbContext context = new EFDbContext();
public void SaveMailRequest(MailRequest mailRequest)
{
context.MailingList.Add(mailRequest);
context.SaveChanges();
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// dispose managed resources
context.Dispose();
}
// free native resources
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
You forgot to put an #ajaxreplace div around the partial:
<div id="ajaxrequest">
#using (Ajax.BeginForm(ajaxOpts))
{
<div id="ajaxreplace">
#Html.Partial("_MailingData")
</div>
}
</div>
You have used this id in your AjaxOptions so you should have a corresponding element in your DOM that will get updated by the result of the AJAX request:
AjaxOptions ajaxOpts = new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "ajaxreplace"
};
As far as your first question about the data being inserted twice into the database is concerned, you haven't provided enough details about your DAL layer so that we could be able to further diagnose the issue. Maybe there's something wrong with your repository.SaveMailRequest method.

Categories