I try to filter out unnecessary properties by casting object to some interface. Next, I serialize it to JSON object like the following code.
#{
var program = ViewBag.Program as IProgram;
}
<script type="text/javascript">
window.program = #Html.Raw(Json.Encode(program));
</script>
However, It still serialize all properties in original object instead of serialize only properties that are defined in interface.
How to serialize only properties in current object type instead of serialize all properties that are in original object?
Use a view model of course:
#{
var program = new
{
Foo = ViewBag.Program.Property1,
Bar = ViewBag.Program.Property2
};
}
<script type="text/javascript">
window.program = #Html.Raw(Json.Encode(program));
</script>
and by the way this is something that should be done inside your controller action, not clutter your view. So instead of passing this object inside ViewBag.Program directly pass a view model that will contain only the properties you need to be serialized.
public ActionResult SomeAction()
{
Program program = ...
ViewBag.Program = new
{
Foo = ViewBag.Program.Property1,
Bar = ViewBag.Program.Property2
};
return View();
}
Oh, and use a real view model and cut that ViewBag out.
Related
This is my controller's code:
IQueryable<Foo> foos = dbContext.Foos.Where(...);
return View(foos);
And this razor code (cshtml) works well:
#model IQueryable<Foo>
#{
IQueryable<Foo> foos = Model;
var projected = foos.Select(e => new
{
fooId = e.FooId,
bar = new
{
barId = e.Foo.BarId
}
}).ToList();
}
#foreach (var x in projected)
{
<span>#x.fooId</span><br />
}
But this razor code (cshtml) doesn't work, being almost the same thing!:
#model IQueryable<Foo>
#{
IQueryable<Foo> foos = Model;
var projected = foos.Selected(Foo.Projection()).ToList()
}
#foreach (var x in projected)
{
<span>#x.fooId</span><br />
}
Foo.Projection() is a static method that I reuse a lot:
public static Expression<Func<Foo, dynamic>> Projection()
{
return e => new
{
fooId = e.FooId,
bar = new
{
barId = e.Foo.BarId
}
}
}
I'm getting that famous error: 'object' does not contain definition for 'fooId', which is discussed in here: MVC Razor dynamic model, 'object' does not contain definition for 'PropertyName' -but none of those answers helped me.
The accepted answer says: "now that MVC 3 has direct support for dynamic, the technique below is no longer necessary", so I also tried to return the projected List<dynamic> to the view ("ready to use, no projection needed") and it didn't work either (getting the same error). This is the code for that attempt:
Controller's code:
List<dynamic> foos = dbContext.Foos.Select(Foo.Projection()).ToList();
return View(foos);
View's code:
#model dynamic
etc.
Edit: With the debugger I'm able to check (just after the exception is thrown) that the item indeed has "a definition for..." (in the example code the item is x, but here is lot)
When you use dynamic you instruct the compiler to use reflection to call methods and access properties. In your case the objects that you access in this way are anonymous types and anonymous types are internal to the assembly they are created in.
The code generated for the Razor view is in a separate assembly and trying to reflect over an anonymous type created in the controller will fail. The debugger is not affected by this limitation so when the reflection fails and throws an exception you are still able to inspect the properties of the anonymous type in the debugger.
This also explains why your code works when you create the anonymous type in the Razor view. Then the code generated by your use of dynamic is able to reflect over the anonmyous type because it is declared in the same assembly.
Essentially, in MVC Razor you are not able to use anonymous types in a view when they are declared in the controller. Your use of dynamic was hiding this underlying problem by generating a run-time error that was hard to understand.
To fix your problem you can either create specific public types instead of using internal anonymous types or you can convert the anonymous type to an ExpandoObject in the controller.
I suppose that View() contructor just don't know what overload to use, since you have dynamic type. You can try to do this:
List<dynamic> foos = dbContext.Foos.Select(Foo.Projection()).ToList();
ViewData.Model = foos;
return View();
But why do you want to use strongly typed View if you don't use any of strongly typed ViewModel advantages, like type check and IntelliSense?
If you really want to pass dynamic type to your View since MVC 3 you can use ViewBag that already is dynamic.
In your Controller:
List<dynamic> foos = dbContext.Foos.Select(Foo.Projection()).ToList();
ViewBag = foos;
return View();
In your View:
#foreach (var x in ViewBag)
{
<span>#x.fooId</span><br />
}
inside the controller
List foos = dbContext.Foos.Select(Foo.Projection()).ToList();
return View(foos);
in side razor view
#model List<dynamic>
I have a partial view in my MVC app which I load into a container dom element. I do this by first calling the controller, like so:
$(container).load('/xxx/GetPartialView');
In the controller I return the partial view:
public PartialViewResult GetPartialView()
{
return PartialView("SomePartial", null);
}
This works just fine. However, I would like to send a parameter (just a simple string value) along from the controller to the partial view I'm creating. This I understand, can be done by the use of a model, like for example:
public PartialViewResult GetPartialView(string someValue)
{
return PartialView("SomePartial", new SomeDummyModel(someValue));
}
But I would like to avoid the model instance if possible, as it seems like a lot of overhead. I want to just send the string value as a parameter. Is that possible?
Instead of passing a custom class such as SomeDummyModel you can simply pass someValue. Assuming that someValue is string from your explanation, that would mean you would accept string in the #model of your partialView.
controller
public PartialViewResult GetPartialView(string someValue)
{
return PartialView("SomePartial", someValue);
}
partial
#model string
<div>Hello, #Model :)</div>
You can also use the ViewData object to pass simple items like that.
public PartialViewResult GetPartialView()
{
ViewData["someValue"] = "hello";
return PartialView("SomePartial", null);
}
And then in the view access it:
<div>#ViewData["someValue"].ToString() :)</div>
This works without a model.
You can put pretty much anything into the ViewData object, you just need to cast it out
#Html.Partial("~/Areas/WO/Views/PartialContent/_FirstPage.cshtml", new ViewDataDictionary { { "WOID", WOID } })
In my Page i am accessing Partial view in the above way.
I need to pass WOID(view data dictionary) value from query string, For that i am using following Code
#{
var desc = Html.ViewContext.HttpContext.Request.QueryString.Get("ID");
Uri referrer = HttpContext.Current.Request.UrlReferrer;
string[] query = referrer.Query.Split('=');
int WOID = Convert.ToInt32(query[1]);
}
But the issue is this code is working in all browsers except I.E. i Need to Solve this problem.
Please help me
Instead of this you can have this value as part of you model and use that.That is the standard and recommeded way .
In your action method you can have these as parameter.Your query string value will get bind to this parameter
public ActionResult ActionMethod(int ID)
{
Model.WOID = WOID;
// Other logic
return View(Model)
}
Next step you can add this as a property to your view model or add it to ViewData dictionary and then access it in your partial view.
In a project I'm working on, I have a lot of "read-only" pages that don't have <form>s in; I also have a lot of form pages that also have a lot of readonly data pulled in from the controller.
Ordinarily you'd use ViewModels and have one ViewModel per view and the ViewModel contains all of the data for that view. That seems fair enough, except there's a problem:
In my head, I see the ViewModel as being a representation and an encapsulation of the entire data sent from the view back to the controller, but the ViewModel might contain data populated by the controller (such as SelectListItem[] Html.DropDownListFor() data) which cannot be populated by a view and sent back to the controller.
Sure, it is possible to have that data as part of the ViewModel and manually re-populate it before returning the View in the Controller's HttpPost-handling method, but I feel it needlessly complicates the controller's code (and you'd have to use UpdateModel() instead of the automatic updates that happen when you specify the model as an argument to the action method).
My solution to this is a typed ViewData object. I derive from ViewPage<TModel> to give ViewPage2<TModel,TData> where TData : ViewDataDictionary<TModel> and override (or shadow) the .ViewData property to return an instance of TData instead..
My questions are twofold:
Subclassing ViewPage seems easy enough, but where do I put the logic to handle the initialisation of my ViewPage2<TModel,TData> class?
Is there anything wrong with my approach?
I found a way to implement my ViewData / ViewModel approach without messing too much with ASP.NET MVC.
Here's how I did it:
This is my ViewPage2 class which exposes the strongly-typed ViewData object to views. Unfortunately you do need to use reflection to avoid the ViewPage's behaviour of creating a brand new ViewDataDictionary, but with statically cached FieldInfo objects it's no more expensive than MVC's dynamic controller/action lookup in routing.
public class ViewPage2<TModel,TData> : ViewPage<TModel> where TData : ViewDataDictionary<TModel> {
public ViewPage2() : base() {
}
private Boolean _dataPresent;
private TData _data;
public new TData ViewData {
get {
if( _dataPresent && _data == null ) {
_data = (TData)base.ViewData;
}
return _data;
}
}
// Cached in static class state for performance.
private static readonly FieldInfo _viewPage1ViewData;
private static readonly FieldInfo _viewPage2ViewData;
static ViewPage2() {
Type viewPage1 = typeof(ViewPage<TModel>);
_viewPage1ViewData = viewPage1.GetField("_viewData", BindingFlags.Instance | BindingFlags.NonPublic );
Type viewPage2 = typeof(ViewPage);
_viewPage2ViewData = viewPage2.GetField("_viewData", BindingFlags.Instance | BindingFlags.NonPublic );
}
protected override void SetViewData(ViewDataDictionary viewData) {
// ViewPage<TModel> creates a new ViewDataDictionary<TModel> when this method is called, even if viewData is of the correct type.
// The trick is to reimplement SetViewData and set base._viewData and basebase._viewData
if( viewData is TData ) {
_viewPage1ViewData.SetValue( this, viewData );
_viewPage2ViewData.SetValue( this, viewData );
_dataPresent = true;
} else {
base.SetViewData( viewData );
}
}
}
Then for each *.aspx file (I use the WebFormViewEngine) I just change the #Page directive:
<%# Page Language="C#" MasterPageFile="~/Site.Master" Inherits="Me.ViewPage2<Me.FormModel,Me.FormData>" %>
I'll admit the two generic type specifiers to make it a bit fiddly, but it's something you only need to set once.
Then in each controller it's just a matter of doing this:
public ActionResult Edit() {
FormData data = new FormData();
data.SomeStronglyTypedField = "foo";
this.ViewData = data;
}
In each view you can now benefit from strongly-typed view data:
<p><%= ViewData.SomeStronglyTypedField %></p>
And because ViewData is not part of the model there's the separation of concerns when dealing with POST-submitted data and the benefits of automatic binding:
[HttpPost]
public ActionResult Edit(EditModel model) {
if( !ModelState.IsValid ) {
// See how I can return the model object without modifying it. All I need to do is re-create the View data.
FormData data = new FormData();
data.SomeStronglyTypedField = "foo";
this.ViewData = data;
return View( model );
}
// persist to DB here
return RedirectToAction("View");
}
In practice I use a common BaseController class that handles the automatic creation and setting of ViewData objects so I don't need those three lines (FormData data... etc) in every action.
I have a view from which I call another view to render some json inside a script tag in my html:
public ActionResult App()
{
return View();
}
public JsonResult SomeJsonData()
{
// ... here goes the code that generates the model
return Json(model, JsonRequestBehavior.AllowGet);
}
inside my App.cshtml file I have something like this:
<script type='text/javascript'>
var myJsonData = #Html.Action("SomeJsonData", "MyController");
</script>
The problem is that sometimes when I reload the page in the browser (using Chrome 20 right now) it shows all the markup, and if I go to the Network tab in the developer tools I can see that the Content-Type of the page request was of type "application/json". If I just reload the page then it loads correctly (the content type is "text/html" as it should be).
Any idea on why does this happen? or what am I doing wrong?
When you return a JsonResult you are modifying the response Content-Type to application/json. So you first invoke the App controller action which returns a View and obviously sets the Content-Type to text/html and inside the returned view you call the SomeJsonData action which craps on the previous content type and modifies it to application/json. Of course the last one wins and that's what the user agent sees at the end of the day: application/json.
So, here's how to proceed:
public ActionResult App()
{
// ... here goes the code that generates the model
var model = ...
return View(model);
}
and in your strongly typed view:
#model MyViewModel
<script type="text/javascript">
var myJsonData = #Html.Raw(Json.Encode(Model));
</script>
Actually I just found another related question
calling #Html.Action for JsonResult changes my response type in parent template
I couldn't find anything before I posted.
The approach I'm gonna take is just changing the content type when returning the json data:
public JsonResult SomeJsonData(bool returnAsHtml = false)
{
// ... here goes the code that generates the model
return returnAsHtml ?Json(model, "text/html", JsonRequestBehavior.AllowGet) : Json(model, JsonRequestBehavior.AllowGet);
}
and on App.cshtml
<script type='text/javascript'>
var myJsonData = #Html.Action("SomeJsonData", "MyController", new {returnAsHtml = true});
</script>
And I'm adding also a flag to allow calling the actionmethod from other places that are expecting an application/json response.
Like other have said, the action that is returning JSON is changing the content-type of the response.
I was able to work around this by using a HtmlHelper to place the JSON into the page. I am avoiding using the controller and action; my HtmlHelper is calling a static method. This solution probably won't work in all cases and is kind of a hack, but you can avoid having to put the data into a bunch of view models this way.
namespace System.Web.Mvc.Html
{
public static class JsonDataProviderHelper
{
public static MvcHtmlString JsonDataProvider(this HtmlHelper helper, JsonDataType jsonDataType)
{
switch (jsonDataType)
{
case JsonDataType.YARDSALE_MINI_CALENDAR:
var yardsales = SalesEventCrud.GetByMonthForJavascript(null, MvcApplication.CurrentPortalId);
return new MvcHtmlString(JsonConvert.SerializeObject(SalesEventCrud.GetByMonthForJavascript(null, MvcApplication.CurrentPortalId)));
default:
return new MvcHtmlString("");
}
}
public enum JsonDataType
{
YARDSALE_MINI_CALENDAR
}
}
}
I am using json.net for serialization. I have the switch statement because I plan to use this helper to return JSON for other situations also.
And in the page:
<script type="text/javascript">
jQuery(document).ready(function () {
setupSmallSidebarCalendar(#Html.JsonDataProvider(JsonDataProviderHelper.JsonDataType.YARDSALE_MINI_CALENDAR));
});
</script>
In the page, the data is written in straight JSON, so no other parsing / massaging of the data is needed.
#Daran's found the cause - the ContentType gets overwritten with whatever was done last. His solution should work, however if you still like the Action method pattern, you should be able to do something like the following and simply change the ContentType before you return. It's still JSON data, but the content type plays nice with HTML views.
[HttpPost]
public JsonResult GetJson(int someId, int foobar)
{
JsonResult result = CreateResult(someId, foobar);
result.ContentType = "text/html";
return result;
}
The downside is that this is, well, a bit weird and/or unexpected behavior. I doubt this will work if you called the endpoint via AJAX, for example. But it looks like it would almost work and that would probably cause some confusion if it's in a shared codebase. For that reason, if you do this, it may be worth naming the endpoint in such a way that it isn't misused. You could even go so far as to create a new type of Result that is designed to work this way rather than use JsonResult.