Passing a ViewBag/ViewData on each page - c#

I have a very large asp.NET MVC application and I'm looking for the most efficient way of accomplishing adding a help document to each view. So in essence,I have added a partial view inside of the layout; that gets called on every page, that invokes a helper method to get return a string and the string is just the URL of the help docs for that specific page.
This is One approach:
{Key: "User_Management", Value: "/Account/User/List"},
{Key: "User_Management_Edit", Value: "/Account/User/Edit"}
The Key would be what i'm looking for as part of the URL and the value would be the returned URL string.
So if the URL is /User/Management/ Then the returned value would be the first of the list, /Account/User/List.
A second approach could be to store the keyUser_Management directly into the view as a ViewBag or ViewData and then check the value in the helper.
My only concern is that with approach One I would have to maintain the keys to make sure that they match the URL, and with approach Two, i'm wondering if it's inefficient to pass a string as a viewbag on each view.
Would love to hear some alternatives or if anyone has a better idea.

An alternative, good or bad...
What about a filter? You could create the below filter and explicitly set help document url on each action. The filter would take care of putting it in the same viewbag key so you didnt have to type it every time and risk typos.
public class HelpDocumentActionFilter : ActionFilterAttribute
{
string helpDocUrl { get; set; }
public HelpDocumentActionFilter(string _url)
{
helpDocUrl = _url;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//whatever viewbag key you wanted with an opportunity to hit the DB or transform the argument
filterContext.Controller.ViewBag.HelpUrl = helpDocUrl;
}
}
usage
[HelpDocumentActionFilter("/help/doc2")]
public ActionResult Index()
{
ViewBag.Title = "Home Page";
return View();
}

Related

Asp.Net Core Error: InvalidOperationException: The view '11c8d3ac-b5f4-47c0-8d68-b1704f2fdc43' was not found [duplicate]

I know this seems pretty basic, and it should be, but I can't find out where I am going wrong. (I hve read other articles with similar titles on SO, and other resources on the web but still cant figure it out).
I have a controller and in it I am setting a string variable. Now I don't mind if this takes the form of a property, an ActionResult, or a straight method. I just want a simple string that I can play with in the controller, and return it to the view.
Essentially what I am trying to do is list the files in a given folder. So my logic is like this:
Find the current folder (partially successful)
Append the path to the where the files you want to located are. i.e. if my current folder is Web\ then I will append something like "Content\CSS" if I wanted to list all the CSS files for example. (I do this because I want to allow the user to dynamically change the site by selecting the css to apply). So it would look like:
CurrentPath += "Content\CSS"
I want load the file names into an array or list
I want to pass this list to my view to render in a combo box (which is on my _Layout.cshtml).
It is important to know that I am trying to view the string on the _Layout.cshtml as I cant just build another view for it. (Unless I am wrong, in that case I would appreicate any help).
At the moment I am still working on getting a simple string passed to my view in a way I can freely manipulate it like in step 2.
I started off with a separate static class and a global variable:
public static class MyTheme
{
public static string CurrentPath = HostingEnvironment.MapPath("~");
}
In my view I had:
#Html.Label(MyProject.Web.Controllers.MyTheme.CurrentPath);
This worked but when I tried to use an if statement to determine if the string was null or empty I got errors. So my next attempts all failed.
Next I decided to bring it into a controller (in this case my BaseController) and this is when I started running into problems. Code below:
Inside BaseController Class
public ActionResult ThemePath()
{
string currentPath = Server.MapPath("~");
if (string.IsNullOrEmpty(currentPath))
{
currentPath = "Error!";
}
else
{
currentPath = "Our Path Is: " + currentPath;
}
return View(currentPath);
}
I dont know how to access and run this from inside my _Layout.cshtml view
So next I tried a standard method inside BaseController:
public string ThemePath()
{
string currentPath = Server.MapPath("~");
if (string.IsNullOrEmpty(currentPath))
{
currentPath = "Error!";
}
else
{
currentPath = "Our Path Is: " + currentPath;
}
return currentPath;
}
Again I don't know how to access it in the view
Finally I tried to use ViewBag and ViewData and now I am just going bonkers! So in my base controller I have:
public string ThemePath()
{
ViewBag.currentPath = Server.MapPath("~");
if (string.IsNullOrEmpty(ViewBag.currentPath))
{
ViewBag.currentPath = "Error!";
}
else
{
ViewBag.currentPath = "Our Path Is: " + ViewBag.currentPath;
}
return ViewBag.currentPath;
}
and in my view I have
#Html.Label(ViewBag.CurrentPath);
or even
#Html.Label(ViewBag.CurrentPath.ToString());
With the following friendly little error messages:
CS1973: 'System.Web.Mvc.HtmlHelper' has no applicable method named 'Label' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax.
Finally I tried ViewData in the base as follows:
public string ThemePath()
{
ViewData["currentPath"] = Server.MapPath("~");
if (string.IsNullOrEmpty(ViewData["currentPath)"].ToString()))
{
ViewData["currentPath"] = "Error!";
}
else
{
ViewData["currentPath"] = "Our Path Is: " + ViewData["currentPath"];
}
return ViewData["currentPath"].ToString();
}
and correspondingly in the _Layout.cshtml I tried:
#Html.Label(ViewData["CurrentPath"].ToString());
Without the .ToString() I get the above error:
With the .ToString() I get a null refrence execption error.
Where am I going wrong?
To pass a string to the view as the Model, you can do:
public ActionResult Index()
{
string myString = "This is my string";
return View((object)myString);
}
You must cast it to an object so that MVC doesn't try to load the string as the view name, but instead pass it as the model. You could also write:
return View("Index", myString);
.. which is a bit more verbose.
Then in your view, just type it as a string:
#model string
<p>Value: #Model</p>
Then you can manipulate Model how you want.
For accessing it from a Layout page, it might be better to create an HtmlExtension for this:
public static string GetThemePath(this HtmlHelper helper)
{
return "/path-to-theme";
}
Then inside your layout page:
<p>Value: #Html.GetThemePath()</p>
Hopefully you can apply this to your own scenario.
Edit: explicit HtmlHelper code:
namespace <root app namespace>
{
public static class Helpers
{
public static string GetThemePath(this HtmlHelper helper)
{
return System.Web.Hosting.HostingEnvironment.MapPath("~") + "/path-to-theme";
}
}
}
Then in your view:
#{
var path = Html.GetThemePath();
// .. do stuff
}
Or:
<p>Path: #Html.GetThemePath()</p>
Edit 2:
As discussed, the Helper will work if you add a #using statement to the top of your view, with the namespace pointing to the one that your helper is in.
Use ViewBag
ViewBag.MyString = "some string";
return View();
In your View
<h1>#ViewBag.MyString</h1>
I know this does not answer your question (it has already been answered), but the title of your question is very vast and can bring any person on this page who is searching for a query for passing a simple string to View from Controller.
Why not create a viewmodel with a simple string parameter and then pass that to the view? It has the benefit of being extensible (i.e. you can then add any other things you may want to set in your controller) and it's fairly simple.
public class MyViewModel
{
public string YourString { get; set; }
}
In the view
#model MyViewModel
#Html.Label(model => model.YourString)
In the controller
public ActionResult Index()
{
myViewModel = new MyViewModel();
myViewModel.YourString = "However you are setting this."
return View(myViewModel)
}
#Steve Hobbs' answer is probably the best, but some of your other solutions could have worked. For example,
#Html.Label(ViewBag.CurrentPath); will probably work with an explicit cast, like #Html.Label((string)ViewBag.CurrentPath);. Also, your reference to currentPath in #Html.Label(ViewData["CurrentPath"].ToString()); is capitalized, wherein your other code it is not, which is probably why you were getting null reference exceptions.
Just define your action method like this
public string ThemePath()
and simply return the string itself.
If you are trying to simply return a string to a View, try this:
public string Test()
{
return "test";
}
This will return a view with the word test in it. You can insert some html in the string.
You can also try this:
public ActionResult Index()
{
return Content("<html><b>test</b></html>");
}

How do I pass an entire model from my Index View to a completely different controller?

Here's the relevant part of my Index view (Index.cshtml):
#foreach (var item in Model) {
<li>
#Html.ActionLink(item.name, "Index", "Filler", new { cap = item }, null)
</li>
}
As you can see, the ActionLink is tied to the Index action on the Filler Controller, and is passing in the entire item (the model)- "item" is of type "capsule".
Now, on my Filler Controller, in the Index action:
public ActionResult Index(capsule cap)
{
var fillers = db.fillers.ToList();
return View(fillers);
}
The capsule class that was automatically generated by Entity Framework is:
namespace CapWorx.Models
{
using System;
using System.Collections.Generic;
public partial class capsule
{
public capsule()
{
this.fillers = new HashSet<filler>();
}
public int pk { get; set; }
public string name { get; set; }
public virtual ICollection<filler> fillers { get; set; }
}
}
The problem is "cap" is NULL in the above Index action. But, if I change the type to "object" instead of "capsule", I do get some weird non-null data, but I can't cast the object to "capsule". Does anyone know why this is NULL?
Thanks,
Mike
You usually just have to pass in the id to the action. For example, can you refactor your code so that it can take in a capsuleId, get the capsule from db and do whatever processing is needed. Adding the entire object to route values in ActionLink doesn't make any sense. Have a look at the link being generated. It is probably just something like ...?cap=Namespace.Capsule as the object would have be ToStringed
The first problem is in MVC you can't bind to an interface (ICollection). You'll need to change it to a List - List<filler>. The second problem you will face is that Lists/Arrays need to be represented in array notation for proper posting, something like name="books[0].book_id". Even though MVC does a lot of magic, the model in your link still has to be represented as a query string eventually.
Depending on what you are trying to do, you may be better off representing your model as a JSON object and posting with .ajax().
See this SO post for other ideas - Need help with binding Set with Spring MVC form
I'm not totally sure why this would work(I think you're nulling out the html attributes), but try to remove the "null" part of the actionlink.
Or, the controller which created the models is wrong.
Again, don't kill me for this.
#Html.ActionLink just generates an anchor element (...), so it makes no sense to attempt to bind a complete object to the routeValues parameter. As #manojlds says, it make much more sense to just pass the relevent key value, since you'll be performing the lookup then anyway (remember, the web is "stateless").

MVC Object Change Tracking

I've currently got an issue where I need to see which fields have been changed on an Edit field for auditing purposes, in which I have code for, but I think my problem lies within my MVC View.
I have (test code):
[HttpPost]
public ActionResult Adjustment(GroupPolicy groupPolicy)
{
if (ModelState.IsValid)
{
_service.SaveGroupPolicy(groupPolicy);
return RedirectToAction("Index");
}
return View(groupPolicy);
}
Which is fine, the Policy saves. However, take this into consideration:
GroupPolicy has, say, 3 fields (in reality there are, maybe, 60):
bool IsPolicy
string Name
string Description
Name and Description are on the form, so that's fine. IsPolicy isn't used on the form, so that gets defaulted to false when posted back to the GroupPolicy object in the Adjustment method.
I can't really put IsPolicy in a Hidden field on the form, as that won't be elegant for 60+ fields in my actual solution, the HTML would be all over the place.
Now that the bool is defaulted to false, it completely abolishes the chance of me knowing if the field has changed or not. All I really want is a method for this data to be preserved, whilst keeping the new information on the Edit form.
Is this possible, am I missing something obvious?
Well first of all, GroupPolicy should be a view model and not an entity - and as such it should be tailored for the view e.g.
public class GroupPolicyViewModel
{
[HiddenInput]
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
...
}
Then in your action you don't need to worry about assigning values that have changed, you just map the view model directly across e.g.
public ActionList Adjustment(GroupPolicyViewModel viewModel)
{
if (ModelState.IsValid)
{
// pull actual entity from service
var groupPolicy = _service.GetGroupPolicy(viewModel.Id);
// update entity from view model
groupPolicy.Name = viewModel.Name;
groupPolicy.Description = viewModel.Description;
...
}
}
This keeps a clean separation between your view & business logic. Also, it allows you to add annotations for client-side validation without affecting your real model.
GroupPolicy has, say, 3 fields (in reality there are, maybe, 60)
I would recommend using AutoMapper for this e.g.
// call this once only e.g. Application_Start in the Global.asax
Mapper.CreateMap<GroupPolicyViewModel, GroupPolicy>();
...
// in your Adjustment action
var groupPolicy = _service.GetGroupPolicy(viewModel.Id);
groupPolicy = Mapper.Map<GroupPolicyViewModel, GroupPolicy>(viewModel, groupPolicy);
_service.SaveGroupPolicy(groupPolicy);
If IsPolicy not on the form then it shouldn't even be part of your model - this will prevent posting of this field into your model and so your check won't even be needed for IsPolicy.
Rather than accepting GroupPolicy as the parameter into the action, create a cut down object GroupPolicyInputModel with only fields that are on the form.
Then use your generic auditing to only compare all the posted fields, as per any other form.

Web API model binding

Given the ASP.NET Web API route:
example/{Id}
Which maps to the following ApiController action method:
public void Example(Model m)
{
...
}
With the model class defined as:
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
}
When I post JSON { "Name": "Testing" } to the URL /example/123 then Id property of the Model object is not bound. It remains as 0 instead of 123.
How can I make the model binding also include values from the route data? I'd prefer not having to write custom model binders for what seems like a common use case. Any ideas would be greatly appreciated. Thanks!
The easiest way to accomplish what you're looking for is just to add another parameter to your method, and specify which parameter comes from which part of the request.
Example:
[HttpPost]
public void Example([FromUri]int id, [FromBody]Model m) {
// ...
}
Now your route of /examples/{id} will successfully be able to bind - because the Example method has a parameter called id which it is looking for in the URI. To populate your model, you can either pass the ID in there as well, or do something like the following:
[HttpPost]
public void Example([FromUri]int id, [FromBody]Model m) {
m.Id = id; // Make sure the model gets the ID as well.
}
Then simply post in { "Name": "Testing" } to /example/123 as you mentioned above.
Our built-in ModelBinding infrastructure doesn't support partial binding of object from both URI and body. You can, however, implement a custom IActionValueBinder to accomplish this task. For example, please refer to the blog post http://blogs.msdn.com/b/jmstall/archive/2012/04/18/mvc-style-parameter-binding-for-webapi.aspx.
The suggested process for this is normally to post to /example instead of /example/{id}. (though with Web API they normally use /api/{controller})
Check out the table half way down this page: http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
Also check this link on "Inserting a record with ASP.NET Web API" - http://stephenwalther.com/archive/2012/03/05/introduction-to-the-asp-net-web-api.aspx
I don't think the default binder can associate your Id route value to your Model Id property, so whatever d1k_is answer is as good as it gets I'm afraid; either that or writing your own binder (you might be able to write a more generic binder maybe? -if you have like a DomainEntity super class or something like that...)

MVC2 DataAnnotations on ViewModel - Don't understand using it with MVVM pattern

I have an MVC2 Application that uses MVVM pattern. I am trying use Data Annotations to validate form input.
In my ThingsController I have two methods:
[HttpGet]
public ActionResult Index()
{
return View();
}
public ActionResult Details(ThingsViewModel tvm)
{
if (!ModelState.IsValid) return View(tvm);
try
{
Query q = new Query(tvm.Query);
ThingRepository repository = new ThingRepository(q);
tvm.Things = repository.All();
return View(tvm);
}
catch (Exception)
{
return View();
}
}
My Details.aspx view is strongly typed to the ThingsViewModel:
<%# Page Title=""
Language="C#"
MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<Config.Web.Models.ThingsViewModel>" %>
The ViewModel is a class consisting of a IList of returned Thing objects and the Query string (which is submitted on the form) and has the Required data annotation:
public class ThingsViewModel
{
public IList<Thing> Things{ get; set; }
[Required(ErrorMessage="You must enter a query")]
public string Query { get; set; }
}
When I run this, and click the submit button on the form without entering a value I get a YSOD with the following error:
The model item passed into the dictionary is of type
'Config.Web.Models.ThingsViewModel', but this dictionary
requires a model item of type
System.Collections.Generic.IEnumerable`1[Config.Domain.Entities.Thing]'.
How can I get Data Annotations to work with a ViewModel? I cannot see what I'm missing or where I'm going wrong - the VM was working just fine before I started mucking around with validation.
I don't think the problem is with the validation.
Change this line;
tvm.Things = repository.All(); //Is this the Linq extension method 'All()'?
to this
tvm.Things = repository.ToList();
I don't know what this is or what it does;
new ThingRepository(q);
It takes a string parameter and returns some kind of Linq IQueriable or List? If that's returning something else it could be causing the problem.
Do you have client-side validation enabled? It might even be a quick hacky-fix, but regarding the error message - it's tough to say without extra info. Could you post your View and the rendered Html?
What does your route for Details look like?
If you set a breakpoint at the start of the Details method, does it get hit when you click on the submit button?
It looks like you could just declare your ThingsViewModel like so:
public class ThingsViewModel: IEnumerable<Thing>
and then implement the interface as appropriate to access the Things list.
I think that ASP.NET MVC might be trying to map your view to the wrong controller. When you return the view you might need to specify the view file name you're trying to use.
return View("ViewName")

Categories

Resources