In ASP.NET web forms it is possible to modify page controls from the master page. For example, on a page "/Sample" I could set TextBox1 as readonly by doing the following.
//Site.master.cs
protected void Page_Load(object sender, EventArgs e)
{
if (Request.Path.ToUpper().Contains("/SAMPLE"))
{
TextBox TB = MainContent.FindControl("TextBox1") as TextBox;
TB.ReadOnly = true;
}
}
The question is... Is there an equivalent way to do this in an MVC application that uses a SiteLayout?
Background: We have purchased an MVC application and have access to modify the
source code. We need to customize the behaviors on some of the pages. It
will only be used by a few dozen people so a performance hit won't really be noticeable. If this was a Web Forms
application we would use the above method. However this application
is written with MVC and it is making our web form programmer (me) confused on how best to proceed. Customizing numerous pages is going to be a headache when
we have to patch the software. Having all the changes in one central location
would be easier to manage going forward. How can you have one place where you can customize other pages programmatically in MVC?
There is no MVC equivalent for FindControl, since views are built in a single operation, where ASP.NET controls are built up and modified over several different events. You don't need to find the control, you specify all of its attributes as it is built.
The rough equivalent to an ASP.NET control (at least in this context) is an HTML helper. HTML helpers are implemented as static extension methods, which allows them to be shared between views and perform some actions as the view is loaded.
using System.Web.Mvc;
using System.Web.Mvc.Html;
public static class MyExtensions
{
public static MvcHtmlString TextBox1(this HtmlHelper helper, string name)
{
if (helper.ViewContext.HttpContext.Request.Path.ToUpper().Contains("/SAMPLE"))
{
return InputExtensions.TextBox(helper, name, null, new { #readonly = "readonly" });
}
return InputExtensions.TextBox(helper, name);
}
}
Usage
~/Views/Shared/_Layout.cshtml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>#ViewBag.Title - My ASP.NET MVC Application</title>
<link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
<meta name="viewport" content="width=device-width" />
#Styles.Render("~/Content/css")
#Scripts.Render("~/bundles/modernizr")
</head>
<body>
<header>
<div class="content-wrapper">
<div class="float-left">
<p class="site-title">#Html.ActionLink("your logo here", "Index", "Home")</p>
</div>
<div class="float-right">
<section id="login">
#Html.Partial("_LoginPartial")
</section>
<nav>
<ul id="menu">
<li>#Html.ActionLink("Home", "Index", "Home")</li>
<li>#Html.ActionLink("About", "About", "Home")</li>
<li>#Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>
</nav>
</div>
</div>
</header>
<div id="body">
#RenderSection("featured", required: false)
<section class="content-wrapper main-content clear-fix">
#* Render custom HTML Helper *#
#Html.TextBox1("test")
#RenderBody()
</section>
</div>
<footer>
<div class="content-wrapper">
<div class="float-left">
<p>© #DateTime.Now.Year - My ASP.NET MVC Application</p>
</div>
</div>
</footer>
#Scripts.Render("~/bundles/jquery")
#RenderSection("scripts", required: false)
</body>
</html>
Note that it is also possible to put the logic directly in the view, but then you cannot reuse logic in other views and it makes your view look cluttered.
As for reading the data back out of a textbox, you need to put it within a <form> tag so it can be posted to a controller action method, which is the rough equivalent of a submit button click event. Unlike ASP.NET, MVC supports multiple <form> tags so you don't have to mix your logic for different actions on the page.
Your question is very broad. But generally, if you want to provide read only rendering for your controls in your razor views based on some conditions, you can try the below approach.
You should add a IsReadOnly property to your view model and use that to render the control the way you wanted.
public class CreateCustomerVM
{
public bool IsReadOnly {set;get;}
//Other properties goes here
public string Email { set; get; }
public string Name { set; get; }
}
In your Action method set the IsReadOnly propery value based on your condition.
public ActionResult Index()
{
var vm=new CreateCustomerVM();
//Set the value based on your condition
vm.IsReadOnly=true;
return View(vm);
}
And in your view , you use the IsReadOnly property to determine whether you want to display a readonly control or not.
#model YourNameSpaceGoesHere.CreateCustomerVM
#using (Html.BeginForm())
{
#Html.TextBoxFor(m => m.Email)
if(Model.IsReadOnly)
{
#Html.TextBoxFor(m => m.Name, new { #readonly = "readonly" })
}
else
{
#Html.TextBoxFor(m => m.Name)
}
<input type="submit"/>
}
Related
I am trying to replace my view components with razor pages but it seems that it's not possible to load a partial razor page because a model is expected to be passed yet it is my understanding that the model for a razor page should be declared in the OnGetAsync method. Here is my code...
Razor Page
#page "{id:int}"
#model _BackgroundModel
<form method="POST">
<div>Name: <input asp-for="Description" /></div>
<input type="submit" />
</form>
Razor Page Code-Behind
public class _BackgroundModel : PageModel
{
private readonly IDataClient _dataClient;
public _BackgroundModel(IDataClient dataClient)
{
_dataClient = dataClient;
}
[BindProperty]
public BackgroundDataModel Background { get; set; }
public async Task OnGetAsync(int id)
{
Background = await _dataClient.GetBackground(id);
}
public async Task OnPostAsync()
{
if (ModelState.IsValid)
{
await _dataClient.PostBackground(Background);
}
}
}
Razor View
<div class="tab-pane fade" id="client-background-tab">
<div class="row">
<div class="col-sm-12">
#await Html.PartialAsync("/Pages/Client/_Background.cshtml", new { id = 1 })
</div>
</div>
</div>
Page Load Error
InvalidOperationException: The model item passed into the
ViewDataDictionary is of type '<>f__AnonymousType0`1[System.Int32]',
but this ViewDataDictionary instance requires a model item of type
'WebApp.Pages.Client._BackgroundModel'
In this example (as per MS recommended approach in their docs) the model is set inside the OnGetAsync method which should be run when the page is requested. I have also tried #await Html.RenderPartialAsync("/Pages/Client/_Background.cshtml", new { id = 1 }) but the same error result.
How can I load the razor page into my existing view?
Microsoft confirmed this cannot be achieved and therefore razor pages cannot be used as a replacement for view components.
See the comments of their docs...
MS docs
#RickAndMSFT moderator15 hours ago
#OjM You can redirect to the page, or you can make the core view >code into a partial and call it from both.
Pages are not a replacement for partials or View Components.
Hi i have just started MVC 4 in c#, i am having issue in rendering partial view.
i have created model for the partial view and controller action in which i am just
sending a single string for the testing reasons but when i try to render it.
it just show the following error.
[NullReferenceException: Object reference not set to an instance of an object.]
here is my controller class.
enter code here
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using my_photos.Models;
namespace my_photos.Controllers
{
public class DefaultController : Controller
{
public ActionResult Index()
{
ViewBag.Massage = "Hello Word";
return View();
}
public ActionResult About() {
ViewBag.Message = "About Page";
return View();
}
public ActionResult _RightView() {
var model = new my_photos.Models.partial(){
Winner = "Shafee Jan"
};
//ViewData["name"] = model;
return View(model);
}
}
here is model class for partial view
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace my_photos.Models
{
public class partial
{
public string Winner { get; set; }
}
}
here my partial view
#model my_photos.Models.partial
<div class="body">
<div class="content">
<div class="header">
<h1 class="nav-heading">Data</h1>
<p class="paragraph" > Winner :#Model.Winner.ToString() </p>
</div>
</div>
<div class="bottom">
<div class="header">
</div>
</div>
</div>
and here is my main layout in shared folder
#model my_photos.Models.partial
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>_Layout</title>
<link href="#Url.Content("~/Content/Style/Site.css")" rel="stylesheet" type="text/css" />
<link href="#Url.Content("~/Content/Style/Side_barnav.css")" rel="stylesheet" type="text/css" />
</head>
<body>
<header>
<h3> Photos app</h3>
<p> In this app my major concerns are with the <strong>theaming</strong>!</p>
<div class="nav">
<ul class="main-menu">
<li>#Html.ActionLink("Home","Index", "Default")</li>
<li>#Html.ActionLink("About","About", "Default")</li>
<li>#Html.ActionLink("Contact","Contact", "Default")</li>
</ul>
</div>
</header>
<section>
#RenderBody()
#Html.Partial("~/Views/Shared/_partial.cshtml")
</section>
<footer>
<div class="fotter-menu">
<ul>
<li>#Html.ActionLink("Home","Index","Default")</li>
<li>#Html.ActionLink("About","About","Default")</li>
<li>#Html.ActionLink("Contact","Contact","Default")</li>
</ul>
<ul>
<li>#Html.ActionLink("Resources","Resources","Default")</li>
<li>#Html.ActionLink("Testimonial","Testimonial","Default")</li>
<li>#Html.ActionLink("Team","Team","Default")</li>
</ul>
<ul>
<li>#Html.ActionLink("Home","Index","Default")</li>
<li>#Html.ActionLink("Home","Index","Default")</li>
<li>#Html.ActionLink("Home","Index","Default")</li>
</ul>
</div>
</footer>
</body>
</html>
when ever i try to get value form the model it give me the following error.
kindly help me through this.
[NullReferenceException: Object reference not set to an instance of an object.]
This is happening because your model is null, as you can see, in actions About and Index there is no model being passed. Also partial is a keyword in C#, it will be better to have a more signifiant name.
To solve this you have to pass a model to every view that uses that layout that is expecting this model. In your case is null and an error is thrown.
public ActionResult Index()
{
ViewBag.Massage = "Hello Word";
var model = new my_photos.Models.partial(){
Winner = "Shafee Jan"
};
return View(model);
}
It is necesary to pass the model because on the below line of code the partial is rendered by default with the current view model.
#Html.Partial("_partial.cshtml", Model) #* Model is passed by default if there is no other parameter specified *#
I think what you really want to do is to call the _RightView action in your layout.
#Html.Action("_RightView", "Default")
Don't forget to modify the action to return a partial view and the passing of model in the other actions won't be necessary
public ActionResult _RightView() {
var model = new my_photos.Models.partial(){
Winner = "Shafee Jan"
};
//ViewData["name"] = model;
return PartialView("_partial", model);
}
hey this is not a big problem you are giving partial class reference to both layout and partial view.
so if you are access a _RightView Action it does't thrown a error because you are passing a object to view properly but when comes it partial view you are not passing object reference so just pass the model in
#Html.Partial("~/Views/Shared/_partial.cshtml",Model)
That's it
In my _Layout.cshtml I want to include a dropdown list in the site header. I'm not positive of the best way to do this, but I've tried to code it using a PartialView. It seems to be working, but when the form is submitted the page loads with only the dropdownlist.
ViewModel:
namespace XXXX_Web_App.Models
{
public class LanguageListPartial
{
[DataType(DataType.Text)]
[Display(Name = "Language")]
public string Language { get; set; }
}
}
Controller:
[AllowAnonymous]
[ChildActionOnly]
public ActionResult LanguageList()
{
ViewBag.LanguageList = GetLanguageList();
return PartialView("_LanguageListPartial");
}
[AllowAnonymous]
[HttpPost]
public async Task<ActionResult> LanguageList(string language)
{
// Save selection to cookie
HttpCookie cookie = new HttpCookie("UserSettings");
cookie["Language"] = language;
cookie.Expires = DateTime.Now.AddDays(-1);
Response.Cookies.Add(cookie);
// Save selection to user profile
if (User.Identity.IsAuthenticated)
{
String userId = User.Identity.GetUserId();
ApplicationUser user = await UserManager.FindByIdAsync(userId);
user.Language = language;
await UserManager.UpdateAsync(user);
}
ViewBag.LanguageList = GetLanguageList();
return PartialView("_LanguageListPartial");
}
public List<SelectListItem> GetLanguageList()
{
List<SelectListItem> languages = new List<SelectListItem>();
languages.Add(new SelectListItem { Text = "English", Value = "en-US" });
languages.Add(new SelectListItem { Text = "Français", Value = "fr-CA" });
languages.Add(new SelectListItem { Text = "Português", Value = "pt-BR" });
languages.Add(new SelectListItem { Text = "Español", Value = "es-MX" });
return languages;
}
Partial View:
#model XXXX_Web_App.Models.LanguageListPartial
#Html.DropDownListFor(
x => x.Language,
new SelectList(ViewBag.LanguageList, "Value", "Text"),
new { #class = "form-control toggle", onchange = "this.form.submit();"
})
_Layout.cshtml:
#using Westwind.Globalization;
#using Westwind.Globalization.Resources;
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>#ViewBag.Title</title>
#Styles.Render("~/Content/css")
#Scripts.Render("~/bundles/modernizr")
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/jqueryUI")
#Scripts.Render("~/bundles/bootstrap")
#RenderSection("scripts", required: false)
<script src="/Scripts/jquery.cookie.js"></script>
</head>
<body>
<div style="">
<div class="header container">
... nav menu ...
</div>
<form action="/Account/LanguageList" method="post" >
#{Html.RenderAction("LanguageList", "Account");}
</form>
<div class="container">
<div class="row">
<div class="col-md-12">
#RenderBody()
</div>
</div>
</div>
<footer class="container">
<hr />
<p>© #DateTime.Now.Year</p>
</footer>
</div>
</body>
</html>
The desired logic is:
Every site/page visit
Anonymous user - load selection from cookie. Default to English. (not done yet)
Authenticated user - load selection from user profile (not done yet)
On selection
Anonymous user - save selection to cookie
Authenticated user - save selection to cookie and update user profile
Like I said, this seems to be working except that when a selection is made the Controller action gets called and when the page reloads the only thing on the page is the dropdown list.
How do I return the View in this situation?
One other question, I would like the text in the dropdown list items to include the culture specific decorations, but they are displaying literally like Français instead. I don't see how I can use Html.Encode() in this situation. It's probably being caused by the way I am adding the items in GetLanguageList(). How do I avoid this?
EDIT
To clarify, my excerpt from _Layout.cshtml above is just that - an excerpt. My _Layout.cshtml contains what you might expect of it - a header with logo and subtitle, navigation menu, and RenderBody() code. The page displays properly on the Partial View's GET Controller Action, but when I make a selection from the dropdown list the POST Controller Action only the dropdown list is displayed on the page - nothing else. _Layout.cshtml is gone and so are the contents of whatever page I am on.
When you submit the form the /Account/LanguageList action is called. It returns with only a partial view:
return PartialView("_LanguageListPartial");
When you return just this, your _layout file is not called.
So what you want is to return another view. Unless you specify it, all your views will contain your _layout.cshtml file. And that already contains the partial view.
So create a new view and return that when you post to the form.
I am changing a create form to become a modal dialog and jquery Unobtrusive validation stops working and don't know how to fix it.
Index.cshtml has a link to trigger a modal dialog.
Create
#section scripts{
<script type="text/javascript">
$('#createCustomer').on('click', function () {
$.get('/Customer/Create', function (data) {
$('#modalContainer').html(data);
$('#myModal').modal({});
});
});
Create.cshtml is a partial view.
#model Demo.Web.Models.CustomerVm
#using (Html.BeginForm("Create", "Customer", FormMethod.Post, new { #id="createForm" }))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Customer</h4>
<hr />
#Html.ValidationSummary(true)
<div class="form-group">
#Html.LabelFor(model => model.Name, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
On the controller there is an actionmethod which returns a partial view for create form.
public ActionResult Create()
{
return PartialView("Create");
}
view modal
public class CustomerVm
{
[Required]
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
before i changed it to be a modal dialog .. everything was working but now i don't know how to fix it. How can i make validation work with dialog? Obviously, I don't want to rewrite validation rules on client script .. i want to get it working from view model .. thanks.
Because the form is not added to the page when the page loads, the unobtrusive validation will not pick it up. There are two ways to fix this.
Include the form on the page during the initial load. This will cause the form to be recognized and validation will occur. You can throw the partial view in a hidden div. Then your JavaScript will just show the modal dialog.
Manually register the form with the unobtrusive validation after adding it to the page. Something like $.validator.unobtrusive.parse("#id-of-the-form");
If you are loading the dialog dynamically just register the unobtrusive validation in the containing element's change event:
$('#modal-container').change(
function() {
$.validator.unobtrusive.parse("#your-form-id");
});
In partialview of create page -> modal-header, model-body, modal-footer and javascript code in the <script>your code </script> - don't put <script>your code</script> in #section Scripts{} and it will work.
Just add the following scripts in your Create form view:
<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>
Adding a new comment to share a more modern solution:
Use BundleConfig.cs in the App_Start folder of your MVC project.
namespace MySolution
{
public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new StyleBundle("~/Content/css").Include(
"~/Content/Site.min.css",
"~/Content/bootstrap.min.css"));
bundles.Add(new ScriptBundle("~/Scripts/js").Include(
"~/Scripts/jquery-3.3.1.min.js",
"~/Scripts/jquery.validate.min.js",
"~/Scripts/jquery.validate.unobtrusive.min.js"));
}
}
}
I am trying to get my clienside validation to work when using an ajax request in a jquery ui dialog.
This means I have no submit button and I do no post back. I could not figure out why I could not get it to work.
I posted this question . I been playing around with it can come to the conclusion is that for whatever reason I need to load up the jquery.validate.unobtrusive with my partial view. It makes no sense to me.
Is this a bug?
Edit
I also gone and posted the error on the asp.net codeplex
The added benefit to this is that if you wish you can just download my little sample application and try it out.
Or you can Follow these steps
Make an non empty asp.net mvc 3 application( I am using razor)
_Layout.cshtml
#ViewBag.Title
<script type="text/javascript">
$(function ()
{
$.get('/home/dialog', null, function (r)
{
var a = $("#dialog").dialog({
resizable: false,
height: 500,
modal: true,
buttons: {
"Delete all items": function ()
{
$('#testFrm').validate().form();
},
Cancel: function ()
{
$(this).dialog("close");
}
}
}).html(r);
});
$('#testFrm').live('submit',function (e)
{
e.preventDefault();
});
});
</script>
</head>
<body>
<div class="page">
<div id="header">
<div id="title">
<h1>My MVC Application</h1>
</div>
<div id="logindisplay">
#Html.Partial("_LogOnPartial")
</div>
<div id="menucontainer">
<ul id="menu">
<li>#Html.ActionLink("Home", "Index", "Home")</li>
<li>#Html.ActionLink("About", "About", "Home")</li>
</ul>
</div>
</div>
<div id="main">
#RenderBody()
<div id="footer">
</div>
</div>
</div>
</body>
</html>
ViewUserControl1 (Partial View) - Stick in Home View folder
#model MvcApplication1.Models.TestModel
#using (Html.BeginForm("test","Account",FormMethod.Post,new {#id="testFrm"}))
{
#Html.ValidationSummary()
#Html.TextBoxFor(x => x.Name)
}
Index (should exist replace with this)
#model MvcApplication1.Models.TestModel
#{
ViewBag.Title = "Home Page";
}
<h2>#ViewBag.Message</h2>
<p>
To learn more about ASP.NET MVC visit http://asp.net/mvc.
</p>
#DateTime.Now
<div id="dialog">
</div>
note the dialog id this will be where the ajax request of the partial view gets stored
Home Controller
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult test()
{
return null;
}
public PartialViewResult Dialog()
{
return PartialView("ViewUserControl1");
}
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
public ActionResult About()
{
return View();
}
}
}
TestModel.cs // view model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.ComponentModel.DataAnnotations;
namespace MvcApplication1.Models
{
public class TestModel
{
[Required()]
public string Name { get; set; }
}
}
web.config
<appSettings>
<add key="webpages:Enabled" value="false" />
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
Now run the application. Click "Delete all items" and "Submit Query".
Nothing should happen. No validation pops up for me.
Now go and add this line to the Partial View(ViewUserControl1)
<script src="../../Scripts/jquery.validate.unobtrusive.js" type="text/javascript"></script>
#model MvcApplication1.Models.TestModel
#using (Html.BeginForm("test","Account",FormMethod.Post,new {#id="testFrm"}))
{
#Html.ValidationSummary()
#Html.TextBoxFor(x => x.Name)
<input type="submit" />
}
Now try again.
It should now come up with validation errors.
I think I found some sort of a work around. Even though I think a automatic way should be done for something so basic as making the validation work with partial views
Work around
This is not a bug. When you load a PartialView using Ajax, you must then parse the unobtrusive validation attributes included in the new elements you are loading.
See http://forums.asp.net/t/1651961.aspx/1?Unobtrusive+validation+not+working+on+form+loaded+by+Ajax