I want to embed the value of Properties.Settings.Default.PreloadGlyph into the mark up of _splash.cshtml in an MVC4 application.
Alas, insertion into this inline JavaScript does not work
spinnerGif.setAttribute("src", "#MyAppNamespace.Properties.Settings.Default.PreloadGlyphUrl");
because the IDE reports that
'MyAppNamespace.Properties.Setting.Default' is inaccessible due to its
protection level.
It's certainly in scope in the controller that requested the view. This is a simplified version of the Index() method.
public ActionResult Index()
{
var psd = Properties.Settings.Default;
return View();
}
How do I get psd into the context for the view, so I can do something like this?
spinnerGif.setAttribute("src", "#psd.PreloadGlyphUrl");
You could just create a new HtmlHelper like that:
HtmlExtensions.cs
public static class HtmlExtensions
{
public static string GetPreloadGlyphUrl(this HtmlHelper htmlHelper)
{
return Properties.Settings.Default.PreloadGlyphUrl;
}
}
Page.cshtml
<h2>#Html.GetPreloadGlyphUrl()</h2>
The views are compiled into separate assemblies dynamically when you refresh any page.
This means that properties on your model need to be public. I presume one of the properties here Properties.Settings.Default.PreloadGlyph must be internal or private hence the fact you can't access it from the view assembly.
Unfortunately you can't use the InternalsVisbleToAttribute here because the assembly is dynamic and you can't tell what it'll be called, therefore the only option realistically is to make your property public (or copy the values to an object with public properties)
I have seen some code which accesses non-public properties using reflection (or a dynamic wrapper which under the hood uses reflection). This may also be an option
Why not create a viewmodel for passing the required data to your view.. You can set MyAppNamespace.Properties.Settings.Default.PreloadGlyphUrl on that and pass it to your view using
return View(new ViewModel { PreloadGlyphUrl = MyAppNamespace.Properties.Settings.Default.PreloadGlyphUrl});
Then you can access the proprety:
#Model.PreloadGlyphUrl
from the model
Related
I noticed almost all code I found just return View(); in [HttpGet] Create action method. I wonder why don't we need to return View(new Person()) for example?
Most of the default constructs you use in a razor view template (like Html.EditorFor, Html.LabelFor, etc) work when the model is null. They can access the meta data of the class type of the model for tasks like displaying the label or deciding on the required format of the value. And they just use an empty value (empty string, 0, false) for the value when the model instance is null.
So, the code you see (View called without model instance) will only run into trouble if there is code in the view template that manually attempts to access the model instance without checking whether it is null.
Still, in my opinion it is best practice to pass a new instance of the Model or ViewModel type when calling a view to create a new object. This way, default values set in the constructor of the Model or ViewModel or values set in the controller action before will not be lost and used in the view. Also, there will not be a problem when someone modifying the view template decides to access the model instance without null checks.
I'm not sure what code you're looking at, but if I'm returning a view that requires a viewmodel, I will pretty much always include the viewmodel object when I call the view.
public ActionResult ViewTime(int id, DateTime? from, DateTime? to)
{
var viewTimeModel = _repository.ViewTime_Read(User, from, to, id);
return View(viewTimeModel);
}
It's possible that the view has some dynamic controls, like grids, etc in it that are responsible for getting their own data, perhaps via javascript calls to the controller methods for json. In that case, the view is pretty much a "dumb shell" and the controls on it are doing the heavy lifting.
I'm working on a Web Application project using C# and MVC that will take in two URLs in a form and use them to create an instance of a class I have created called "ImageSwap." This model has some data (a username of the person performing the swap; two variables which hold the URLs of two images to be swapped; two variables which save the actual names of these files without all of the rest of the URL information; and two arrays which represent the file locations to check for these files). Right now, I have it so that the initial index view creates the instance of the class and passes it to the same view with the information put in through the form, submitted via POST, like so:
public ActionResult Index()
{
Ops.Operations.Models.ImageSwapModel newImageSwap = new Models.ImageSwapModel();
return View(newImageSwap);
}
[HttpPost]
public ActionResult Index(ImageSwapModel imageSwap)
{
var oldFileFound = false;
var newFileFound = false;
if (ModelState.IsValid)
{
//Perform data manipulation and set needed values
}
}
It then performs some functions on the data, such as parsing out the filename at the end of the URL, and a directory number (which is the first part of this filename, i.e. directory#_fileName.jpg). All of this works fine.
My problem is that I would like to pass this model to another view once it has data populated in all of its fields by this initial ActionResult so that I can have a verification view, which would allow the user to preview the two files side by side so that they can ensure they are swapping the appropriate images. They should then be able to hit another submit button which will initiate the actual moving/replacing of the images and be taken to a page confirming.
Is there a way to pass data from this controller to a different view? My confusion arises because I cannot create another version of an ActionResult of Index with the same input, but I do not want to have the actual swapping of the images occur without a preview and a prompt. Should I re-write my Index view so that it utilizes partial views in order to accomplish this? What is the easiest way to have data persist through multiple steps and views?
What is the easiest way to have data persist through multiple steps
and views?
Your question sounds like you're trying to achieve what you can easily do with sessions. The session object allows you to persist data between requests simply by adding it to the Session object on the HttpContext that exists within the base class that your controller extends, like so:
(Note the Serializable attribute. This allows your object to be serialized into the session object).
[Serializable]
public class ImageSwapModel {
// Your class's properties
}
Then in your controller you can do the following:
[HttpPost]
public ActionResult Index(ImageSwapModel imageSwap)
{
var oldFileFound = false;
var newFileFound = false;
if (ModelState.IsValid)
{
this.HttpContext.Session["ImageSwap"] = imageSwap;
}
}
When you want to retrieve the model you can grab it from the session like so:
var imageSwap = (ImageSwapModel)this.HttpContext.Session["ImageSwap"];
Taking it one step further:
Whilst the above will work fine, generally it's not a good practice to reference the HttpContext object directly in your code as it creates unnecessary coupling to the HttpContext object that can easily be avoided. Instead you should opt to inject an instance of the session object via Dependency Injection. Here is a similar answer that provides a basic idea as to how you can do this.
You can return different views with Models being passed to them in your one Index action like
if(some condition)
{
Return View("ViewVersion1", MyModelVersion1);
}
else
{
Return View("ViewVersion2", MyModelVersion2);
}
I am reading a book on ASP.NET MVC and I'm wondering how the following example works:
Example #1
Controller
public class MyController : Controller
{
public ActionResult Index()
{
ViewBag.MyProperty = 5;
return View();
}
}
View
<h1>#ViewBag.MyProperty</h1>
Now I understand that ViewBag is a dynamic object, so that's how you can set the property (though I don't know much about dynamic objects, never worked with them.) But how does the view get the specific instance of the ViewBag from the controller, even though we don't pass anything directly?
I thought that the ViewBag could be a public static object, but then any change to it would be global and it wouldn't be specific to a view instance.
Could you elaborate as to how this works behind the scenes?
Example #2
Controller
public class MyController : Controller
{
public ActionResult Index()
{
ViewBag.MyProperty = 5;
return View();
}
public ActionResult Index2()
{
ViewBag.MyProperty = 6;
return View();
}
}
Now let's say the Index method is called first, and then the Index2. In the end the value of ViewBag.MyProperty will end up as 6 (the value from Index2). I feel that it is not a good thing to do, but at the same time I feel that I'm thinking in desktop development terms. Maybe it doesn't matter when used with ASP.NET MVC, as the web is stateless. Is this the case?
ViewBag is a property of ControllerBase, which all controllers must inherit from. It's a dynamic object, that's why you can add new properties to it without getting compile time errors.
It's not static, it's a member of the object. During the request lifetime, the controller instance is created and disposed, so you won't have "concurrency" problems, like overwriting the value.
The View (and its variants) method is not static as well, and this is how the view receives the ViewBag values: during the process of rendering the view, the controller instance has its ViewBag instance as well.
If you would analyse ControllerBase class you would see that ViewBag property is a "proxy" to ViewData property just to make your source look nicer. (I even remember Scott Hanselman taking interview from Phil Haack where Phil introduced ViewBag property as a shortcut to ViewData and eliminating the need of repeated square brackets and quotes). Even though ViewBag property is exposed as dynamic object it implements a DynamicViewDataDictionary class which works directly with ViewData.
Looking at source code of Controller class you can find this method:
protected internal virtual ViewResult View(string viewName, string masterName, object model)
So basically when you call return View(); from your controller it creates a new instance of ActionResult class passing ViewData from controller to it's constructor. Instance of ActionResult is then passed to a particular view engine (ASPX, Razor) so it could be used to render a view in question.
Making ViewBag/ViewData public static could be harmful. Each web request to your MVC application creates a new instance of controller. If you'd have ViewData/ViewBag as public static then two concurrent users would share same data from ViewBag/ViewData.
Here is a video. Discussion on ViewBag (formder ViewModel) starts at 04:05
ViewBag is a property of ControllerBase. It is defined as follows:
public Object ViewBag { get; }
Note that this signature is actually incorrect. Here's what the source code actually looks like:
public dynamic ViewBag {
get {
if (_dynamicViewDataDictionary == null) {
_dynamicViewDataDictionary = new DynamicViewDataDictionary(() => ViewData);
}
return _dynamicViewDataDictionary;
}
}
_dynamicViewDataDictionary is an ExpandoObject; you can add properties to it at runtime. Its lifetime is the same as that of the controller, which is the lifetime of the HTTP request.
Inside a loop I'm trying to add an object with the type Location to a List<Location> property.
CONTROLLER:
[HttpPost]
public ActionResult Index([Bind(Include = "CitySearch")]HomeViewModel model)
{
List<CityIdentity> ids = null;
ids = service.GetNamesId(model.CitySearch);
foreach (var id in ids)
{
var loc = service.GetLocation(id.CityId);
var location = new Location
{
CityId = id.CityId,
Place = loc.Place,
};
model.Locations.Add(location);
}
}
VIEWMODEL:
public class HomeViewModel
{
public string CitySearch { get; set; }
public List<Location> Locations { get; set; }
}
model is of type HomeViewModel and property Locations is of type List<Location>. model is instance from form HttpPost action from submitting the form in the View.
I'm debugging and the error's occurs at model.Locations.Add(location) and I'm getting object reference not set to an instance of an object. and nullref exception.
Any idea?
Based on your reactions and the context, it seems your Locations property isn't initialized at the time, so you can't use a method Add on this collection. To overcome this issue, you need to initialize this property first and then you can manipulate it whatever way you need to.
One way of achieving this is to initialize your Locations property in the constructor of your model (or wherever you see fit).
So just add something like this and you're good to go:
Locations = new List<Location>();
This initializes your property enough to use its methods.
Any idea?
Yes, either the model variable is null or the Locations property is null. So make sure that they are not null before accessing model.Locations. Otherwise the exception you are getting is perfectly normal. You cannot call methods on a null instance. By the way you could have used standard debugging techniques (putting a breakpoint in your code, running your application in Debug mode and inspecting the values of your variables) would have lead you to the same conclusion.
Now why your model or model.Locations property is null is a hugely different question. I guess that the form you submitted didn't contain input elements that obey the standard naming conventions for binding to a List. I invite you to familiarize yourself with those conventions and respect them if you expect the default model binder to be able to rehydrate your models in the POST actions.
Maybe somehow you put the model as action argument and expected that ASP.NET MVC will automagically populate it from somewhre? No, that's not how ASP.NET MVC works. There's no magic. ASP.NET MVC uses model binders which have strict rules. Only what you send as arguments to the action is what you can expect to get back.
When returning strongly typed models for views such as Create and Edit (when validation of the object we are editing fails) I usually prepare the models like this:
//
// GET: /Invoice/Create
public virtual ActionResult Create()
{
// prepare the empty model
Invoice model = new Invoice();
model.Client = new Client();
model.Client.PostCode = new PostCode();
return View(model);
}
//
// POST: /Invoice/Create
[HttpPost]
public virtual ActionResult Create(Invoice document,
FormCollection collection)
{
// check for errors
if (!ViewData.ModelState.IsValid)
{
document.Client = new Client();
document.Client.PostCode = new PostCode();
return View(document);
}
Now I know that this is how others do it too, in fact you can see this same approach in MVC Music Store sample and others. However, this is very error prone because one might accidentally left out a referenced entity which is required in the view. It also requires too much thinking about view/model interaction. What I would want is some sort of automatism. Value typed properties in models usually aren't the problem because they default either to zero or empty strings. Reference types however should be initialized with new..but sooner or later we end up with code blocks that are being repeated, reference type properties being left out, etc..And I don't think it's good coding practice either.
What are other options we could take?
UPDATE:
Because replies kinda missed the point (they do not relief us of thinking about models in any way and require additional code in model classes), I was thinking if this option would work:
Use custom Action filter,
override OnActionExecuted()
use Reflection inside this method to take out the object from the Model and enumerate its public properties and try to initialize them.
I have steps 1, 2 and 3 partially implemented but I cannot figure out how to do "... = new Client();" programatically with Reflection.
Make the properties of your model return a new instance if it is null
private Client client;
public Client Client
{
get
{
if (client == null)
client = new Client();
return client;
}
}
I suggest that you use a Strongly typed view bound to a ViewModel that is distinct from the Domain Model you're trying to create, and put whatever necessary logic into the constructor of the ViewModel
I'm not sure I fully understand your question. You want what automated? ViewModels and Views? Are you creating strongly typed views?
I have created a T4 template that I point to a database and it generates a ViewModel for every table. Foreign keys become drop down lists, long strings get a TextArea instead of TextBox, etc. I then delete the ones I don't need and modify the ones I want to keep. It's not a totally automated process, but it does 80 to 90 percent of the work, depending upon the project.
Then I generate strongly typed Views from those ViewModels.
It also sounds like you might be interested in AutoMapper.