I'm building a state machine which display different action controls (partial views) on the page according to some dynamic value.
I started writing HtmlHelper Extension methods to ouput the proper html for each state. Something like:
#if(Model.state == "NEW") {
Html.RenderActionEdit()
Html.RenderActionDelete()
}
And to do this I was doing simple methods in the form:
return MvcHtmlString.Create("<form><input>..... </form>");
But this is rather cumbersome for large bits of html. So, the question is, would it be possible to write this Html on separate views (cshtml files) and then somehow load them and pass the result to MvcHtmlString? Like
return MvcHtmlString.Create(View.Load("EditAction.csthml"));
I couldn't find a way to load an existing view and then just "include" it on the partial method's output.
Many thanks for any help!
There are a couple of ways to do this:
#Html.RenderPartial("thepartial.cshtml", model); will pass model to the partial view, and render it. There are a couple of other versions too.
#Html.Action("action", "controller", id) (see msdn) will pass id to the specified action method, and render the view it outputs. This is very convenient if you don't have the model object needed for the partial available in your main view.
In an extension method on HtmlHelper, you could use it like this:
public HtmlString YourContent(this HtmlHelper helper)
{
return helper.Action("action", "controller", new { id = 1 });
}
which in your view would be used by calling #Html.YourContent().
Related
In MVC 5 I am attempting to use the controller to render a partial view only if the (Windows Authenticated) user belongs to one or more of a list of Active Directory groups. There are over 30 distinct groups I need to account for, so the "hello world" examples don't fit my needs. After playing scavenger hunt on the web, I managed to collect this much. No compile or runtime errors, but the content is showing for all users rather than the specific users. So the desired outcome is not yet achieved.
While I can achieve the desired outcome using if-then logic in the view, it creates a lot of unnecessary duplication and encourages spaghettification. So I'm trying to do this in the controller.
Summary of Desired Outcome:
When the user loads the viewpage, the partial view should only render if the Windows Authenticated user belongs to one or more of a list of groups defined in the controller action. If the user is not authorized, then the partial view is not included.
Controller Block:
[ChildActionOnly]
[Authorize(Roles="Domain\\GroupA,Domain\\GroupB")]
public ActionResult MonitorCSU()
{
return PartialView("MonitorCSU");
}
View Block:
<div class="rowWithCols3">
#Html.Partial("MonitorCSU")
Unsuccessful Iterations:
In the controller block I tried (unsuccessfully) to use an if-then block, the else case being another partial view with no content.
[ChildActionOnly]
public ActionResult MonitorCSU()
{
if (User.IsInRole("Domain\GroupA")) {
return PartialView("_MonitorCSU");
}
else
{
return PartialView("_Unauthorized");
}
}
In Razor, I tried using HTML.Action but when I tried run the page the browser hung in an infinite loop.
#Html.Partial() returns a partial view without calling a controller method. In order to call your controller method, you need to use
#Html.Action("MonitorCSU")
or
#{ Html.RenderAction("MonitorCSU") }
Note this assumes that the MonitorCSU() method is in the same controller as the method that generates the main view (other wise you also need to include a parameter for the controller name)
Refer documentation
While you've found a solution, you're going to have other problems with it. I would suggest a different approach, which is to use EditorTemplates and create a separate model for the html you want to render. Then, at runtime you would check whether the user is in the groups you specify, and if they are, you create an instance of the model, and if they are not you leave the model null. In this way, when the view is rendered with EditorFor(), it will ignore and not render the template for users who do not have access.
Thanks to #Stephen Muecke and and a commenter whose entry has mysteriously vanished, I have the missing pieces.
I was able to test this code with several real users and verified the desired behavior happens consistently.
Controller Block:
Main difference: take out authorization and use an if-then block send one of two partial views.
[ChildActionOnly]
public ActionResult MonitorCSU()
{
if (User.IsInRole("DOMAIN\\GroupA"))
{
return PartialView("MonitorCSU");
}
else
{
return PartialView("Unauthorized");
// this is an empty page
}
}
View Block:
The key difference is using HTML.Action
<div class="rowWithCols3">
#Html.Action("MonitorCSU")
I looked around and couldn't find an easy solution.
I've tried #GetUserName which doesn't work.
I've tried # { GetUserName which doesn't work.
There has to be an easy way to call a method from the razor view engine.
It is within a foreach loop.
I need GetUserName(item.userID)
The below code is in my controller:
[ChildActionOnly]
public string GetUserName(int userID)
{
ProPit_User user = db.ProPit_User.Find(userID);
return user.username;
}
Trying to call a controller action method directly from your view is usually a sign of bad design.
You have a few options, depending on what you are trying to do:
Avoid calling the method, instead put the data in your model, and render the model
Use Html.RenderAction
Put the method in another class and use normal function syntax.
(1) is usually my default approach, often incorporating DisplayTemplates and EditorTemplates
For (3), e.g.
public static class Util
{
public string MyUtilMethod(int blah)
}
And the view:
#Util.MyUtilMethod(1)
Although you can obtain the controller instance from your view, doing so is plain wrong as it violates the whole MVC (and MVVM) paradigm, as the view should not be aware of its controller.
(The only possible reason I can think of where this would be useful would perhaps be for testing the controller from a mocked view, although even here, it should be possible to test the exposed controller functionality directly from unit tests):
#{
var controller = ViewContext.Controller as MyController;
var userName = controller.GetUserName(123);
}
The better way to have arrived at this result is for the controller to pre-populate, and pass all the data needed by the View, such as the userName, to a custom ViewModel (as typed by the #model directive at the top of the Razor page), or to the ViewBag dynamic.
I am doing some complex logic involving loads of recursion to create a (complex) piece of HTML.
I started off doing this in the View using functions in Razor because I felt as HTML it belonged there.
But as it started getting more complex I thought I would rather do it in back-end code. Which it currently is.
It still feels a bit smellish though, and I am wondering if I should move it to the View again (which obviously clutters the view)
Which technically is more correct? When is it appropriate to use back-end code to generate HTML?
Thanks for your input.
create more granular partial views and partial models to maintain MVC pattern.
when your correctly select a model for your view, even partial, generating HTML in view is not a problem. you may end up with 20 views and 5 more models, but controller will be just selecting views and populating models which is good.
Don't do it in controller. You can extend the HtmlHelper class and do the stuff there. For example if you are using a paging helper.
Create a static class HtmlHelpers
namespace YourMvcApplication.WebUI.HtmlHelpers
{
public static class PagingHelpers
{
public static MvcHtmlString PageLinks(this HtmlHelper html,int totalPages)
{
StringBuilder result = new StringBuilder();
// do the complex logic to create dynamic html and append to
// String Builder
return MvcHtmlString.Create(result.ToString());
}
}
}
Add reference to this class in all views in web.config.
<namespaces>
<add namespace="YourMvcApplication.WebUI.HtmlHelpers"/>
</namespaces>
Use and resuse this Html Helper methods wherever required.
<div>
#Html.PageLinks(Model.TotalPages)
</div>
I'm trying to create a HtmlExtension to retrieve the name of the current view.
However, I don't want to have the requested view (e.g. "LogOn" for "/Account/LogOn") but the actual file that is being processed (e.g. "_Layout").
The closest I could find was html.ViewDataContainer.ToString() which returns {ASP._Page_Views_Shared__Layout_cshtml} for example, but I don't think that parsing this would be a great idea.
Is this information available in the Html Extension?
Thanks in advance.
You can create an extension like this,
public static string ViewName(this WebViewPage page)
{
return Path.GetFileNameWithoutExtension(page.VirtualPath);
}
then from a razor view,
The view name is: #this.ViewName()
or without extension,
The view name is: #Path.GetFileNameWithoutExtension(VirtualPath)
The current code of one of the views in my project is a large, monolithic one and I just want to organize it. Is it just fine to place methods inside my ASP.NET MVC view?
I would say definitely not. The purpose of MVC is to separate the concerns i.e. Model - View - Controller. In your case you are mixing Model/Controller logic with your View.
If you need to pass complex content into your view you should create custom ViewData classes, populate them in your controller and pass them into your View.
If your methods are more relating to generating View markup you should look at splitting it up into partial views or as already suggested using helper extension methods.
It's possible but if you need methods inside a view, perhaps you should instead consider extending the Html object with an extension method, and then use these methods from inside the view.
I like to separate my extensions by functionality to keep the view readable.
For example:
public static MySpecialDateHelper SpecialDateHelper(this HtmlHelper helper)
{
return new MySpecialDateHelper(helper);
}
public class MySpecialDateHelper
{
// Fields
private HtmlHelper m_helper;
private StringBuilder m_sb = new StringBuilder();
// Methods
internal MySpecialDateHelper(HtmlHelper helper)
{
this.m_helper = helper;
}
// Print date prettily
public public string PrettyDate(datetime target)
{
// format however
return string.format("pretty {0}", target.ToString());
}
// Print date prettily
public public string PrettyDateInATextbox(datetime target)
{
return m_helper.TextBox("prettyid",PrettyDate(target));
}
// etc...
}
Then in your view, you just have to reference the extension you just created
<%= Html.SpecialDateHelper.PrettyDateInATextbox(Now); %>
Of course, your separation of methods into extensions may vary, you can also extend the HtmlHelper object with the methods directly, but i prefer this method
No.
Move it the controller, if necessary introduce view models. After the that, define your own Html helpers, considering those should be for html like stuff, not for any logic.
You could have something in a view that's v. v. specific to it, and its just not possible that bit of code would do anything outside of it. But even then, you can just define it in a helper, as long as the name is very clear on what it does, you are better than putting it in the view. You can unit test helpers, so there is a clear advantage when doing that.