I'm trying to understand what the equivalent of a user control is within an MVC application? From what I understand, it's a Partial. The problem I'm finding is that when I use a user control, I'm able to encapsulate the logic within that control and re-use it across multiple pages without having to worry about the parent page.
How does this work with MVC? There doesn't seem to be any way to encapsulate logic with a partial view.. in fact it's confusing the hell out of me.
Does this mean that for any page that might use the partial, the controller would always have to return the data for the partial just in case it's required? Doesn't this seem incredibly inefficient? I can't seem to wrap my head around how this works...
You can add logic using razor syntax. For example, you can use conditional statements to manipulate the final output.
#{
if(whatever == true)
{
<p>Whatever is true</p>
}
else
{
<span id="someId">
<label for="enterName">Enter name:</label> <input type="text" id="enterName" />
</span>
}
}
In addition to this you can use #helper syntax like
#helper MakeLink(dynamic id)
{
<div id="actionButtons">
#Html.ActionLink("Some Title", "Some Action", new {id = id}) |
#Html.ActionLink("Another Title", "Another Action", "Some Controller", new {id = id})
</div>
}
The logic in partials is slightly different to what you are used to in Web Forms user controls. The above logic can be placed in a Partial so that you can reuse it later, you just need to place it in /Views/Shared/DisplayTemplates or EditorTemplates.
Related
Situation: I have an ASP.NET Razor page that is constructed of partials.
Some of these are in loops iterating over collections that are members of the main page's view model. These iterated partials use the collections' member types as their own view model.
So the main page Razor looks something like this:
#model MyProject.MainViewModel
#*Other stuff...*#
#foreach (Subtype v in Model.SubViewModel)
{
Html.RenderPartial("MyPartial", v);
}
And the partial looks like this:
#model Subtype
#Html.HiddenFor(t=>t.ParentViewModelID)
#Html.EditorFor(t=>t.Field1)
#Html.EditorFor(t=>t.Field2)
This all works well on initial page load, but I'm wondering how I should handle dynamically adding these partials via AJAX.
Suppose the user presses a button that's ultimately intended to add to one of these view model collections. I can AJAX back to the server, render the appropriate partial there, and return it to the client:
[System.Web.Mvc.HttpGet]
public ActionResult AddSubtype(int mainViewModelId)
{
var model = new Sub { ParentViewModelID=mainViewModelId};
return PartialView("MyPartial", model);
}
And then I use a bit of script to stick this into the DOM where it belongs:
success: (data) => { $('#subtypeHanger').append(data);},
This seems to work, except for one big part: when I evntually submit my form, I'm not seeing the data associated with this dynamically added partial in my view model.
I have been trying to debug this, but I'm wondering in broad terms whether this is possible- or alternately, how much MVC is supposed to abstract over such things. Obviously I can do things with JavaScript to force this to work. Is there an ASP.NET MVC pattern for this I should emulate, though?
EDIT: I'll mention some things I've tried since I posted this:
Managing the "name" and "id" attributes of the partial views' edit controls manually, so that they end up looking like they do when I nest these controls in the main view instead of a partial (i.e. longer, with more text and an index).
Using the main view model class for the partials. This makes me move the loops into the partial. The "name" and "id" property look like what I was going for in #1 above, but this design greatly complicates the process of returning the partial from my AJAX calls.
Using "for" loops instead of "foreach." By itself, this doesn't achieve what I want.
All this said, if #1 or #2 is the best pattern for what I'm doing, perhaps I can make that work.
"Situation: I have an ASP.NET Razor page that is constructed of partials."
This is called "Master Details"
check
Master Details CRUD with MVC 5 | The ASP.NET Forums
The trick is naming your Fields
#Html.HiddenFor(t=>t.ParentViewModelID ,new { htmlAttributes = new { #Name = "[" + i + "].ParentViewModelID" } })
#Html.EditorFor(t=>t.Field1 ,new { htmlAttributes = new { #Name = "[" + i + "].Field1" } })
#Html.EditorFor(t=>t.Field2 , new { htmlAttributes = new { #Name = "[" + i + "].Field2" } })
so it will be
<input type="text" name="[0].Field1" />
<input type="text" name="[0].Field2" />
<input type="text" name="[1].Field1" />
<input type="text" name="[1].Field2" />
and better(for ASP.NET mvc 5 Model Binding) if you use
public ActionResult Edit(MainViewModel master, List<Subtype> details)
if you provided the Full model i will provide a full working example
I have an asp.net-mvc website where up until now there have been no entitlements as its been open to everyone. Many of the pages are detailed forms with textboxes, select dropdowns, etc
I now need to change this to make many of the existing pages "entitled" so only certain people have edit capability and everyone else sees a read only page. I am trying to figure out if I should
Create a seperate view for everyone one of my existing views with forms that is just read only html on a page and redirect based on entitlements on the server side, something like
public ActionResult OrderView()
{
var isEntitled = Model.IsEntitled()
if (isEntitled)
{
return("OrderEditableView", GetViewModel());
}
else
{
return("OrderReadOnlyView", GetViewModel());
}
}
or
Reuse the same view and simply disable or hide the "Save" button on the screen.
on my view have
<% if (Model.IsEntitled) { %>
<button id="saveButton">Save Changes</button>
<% } %>
The second option would be much quicker to implement but would be a little weird because it would look like you could edit all of the fields but just dont' see (or see a disabled) Save button on the form.
The first option seems cleaner but I would have to go and create a new view for everyone of my screens and that seems like a lot of work (over 100 views currently)
This seems like a common situation so I wanted to see if there was a best practice on dealing with this situation. Obviously I am looking for a solution given the current situation that I am in but I would also be interested if there were patterns or solution that would be considered best practice or recommended if I was starting from scratch.
I would go for creating a separate partial/full views for both.
Reasons:
easy to change and maintain
validation code only in editable part
clear separation of concerns
cleaner code
may be we can reuse some of the code by abstracting/separating similar code to re usable partial views
easy control over access
Also, in real world we balance among budget,time and effort. so considering that I may use mix of these depending upon the situation.
I'd go for the second options with a single view for both writable and readonly forms.
For the visual side, I'd implement a few things in addition to hiding the Save button:
Set the input fields to readonly. You can easily do this with client side Javascript.
Change the colors (and possibly other attributes) of the input fields to emphasize that they are readonly (e.g. remove the borders). This can be easily achieved by a style sheet and a single additional class name in the form tag.
The authorization on the server side is simple as well. Just annotate the action accordingly.
It could look like this (simplified, using Razor and jQuery):
View:
#using (Html.BeginForm("AddressController", "SaveChanges", FormMethod.Post,
new { #class = Model.IsEntitled ? "regular" : "readonly"}))
{
<p>
#Html.TextboxFor(model => model.Name)
</p>
<p>
#Html.TextboxFor(model => model.Address)
</p>
#if (Model.IsEntitled) {
<p>
<button id="saveButton">Save Changes</button>
</p>
}
}
<script type="text/javascript">
$( document ).ready(function() {
$('form.readonly input').attr("readonly", true);
});
</script>
Controller:
public class AddressController : Controller {
[Authorize(Roles = "xyz")]
[HttpPost]
public ActionResult SaveChanges(Address address) {
...
}
}
CSS:
.readonly input {
border: none;
}
an option is to do the switch in the view..
<% if (Model.IsEntitled)
{
Html.Partial("OrderEditablePartialView", Model)
}
else
{
Html.Partial("OrderReadOnlyPartialView", Model)
}
%>
another option could be to create custom Html helpers to render the elements as editable or readonly, which would give you some flexibily on how you handled each element.
crude example:
public static MvcHtmlString FormTextBox(this HtmlHelper helper, string name, string text, bool editable)
{
return
new MvcHtmlString(
String.Format(
editable
? "<input type='text' id='{0}' name='{0}' value='{1}'>"
: "<label for='{0}'>{1}</label>",
name, text));
}
The quickest solution would be some javascript that disables all input elements on pages where the user isn't entitled. How to implement this is up to you, it depends on how your views and models are structured.
This way you can reuse the edit-views without even having to edit them, but depending on the kind of user you have you can expect "site not working" calls when your forms appear disabled.
patterns or solution that would be considered best practice or recommended if I was starting from scratch.
The proper way to go would be DisplayTemplates and EditorTemplates with proper viewmodels, but from your "create a new view for everyone of my screens and that seems like a lot of work" comment I gather you're not interested in that.
So I'd go with the former. Please make sure you check the permissions in the [HttpPost] action methods, as malicious users can easily re-enable the form and post it anyway.
So far:
I have a page with multiple submits on it, where each submit depends on the previous one.
The same page is rendered each time a submit is clicked.
I found myself writing spaghetti code in the controller method (branching based on the ViewModel), and wanted to factor out the behaviour for each submit into a separate method.
I implemented the solution found here - specifically the solution posted by mkozicki based on the article by Maartin Balliauw.
This worked well for forking to different controller methods. But I encountered two problems:
Returning to the same view each time.
Hard-wiring the action method names in the View.cshtml
Here's the code:
Controller:
public class PlayerStatController : Controller
{
public class PlayerStatViewModel . . . //quite complex ViewModel
// HTTP GET
public ActionResult SelectPlayer()
{
List<string> idx_list = getSeasonIndex();
return View(new PlayerStatViewModel(idx_list));
}
// One of three forked action methods
[HttpPost]
[MultipleButton(Name = "action", Argument = "ChosenSeason")]
public ActionResult ChosenSeason(PlayerStatViewModel viewModel)
{
List<string> team_idx = getTeamNameIndex(viewModel.selected_seasonIndex);
return View("SelectPlayer",new PlayerStatViewModel(new List<string>(), team_idx, new List<string>(), 0));
}
Here an excerpt from the view (SelectPlayer.cshtml)
<form action="/PlayerStat/ChosenSeason" method="post">
<fieldset>
<legend>Select Season</legend>
<div class="editor-field">
#Html.LabelFor(m => m.selected_seasonIndex)
#Html.DropDownListFor(m => m.selected_seasonIndex, Model.seasonIndex_select_list)
#Html.ValidationMessageFor(m => m.selected_seasonIndex)
</div>
<p>
<input type="submit" value="Choose Season" name="action:ChosenSeason" />
</p>
</fieldset>
</form>
Hence:
Is returning from the forked action method with return View("SelectPlayer",new PlayerStatViewModel(...); the best solution to forcing the same view (SelectPlayer.cshtml) to be rendered every time?
Is there a way to avoid hard-coding the action method name in the View (i.e., <form action="/PlayerStat/ChosenSeason" method="post">) I would like to keep using #using (Html.BeginForm()) if possible.
Specifying the view name in the return statement is the best and most practical way to return a view that is named something different than the current action method being executed. I believe this is by design in order to decouple action methods from a single view.
Again, for the view if you want the form to post to an action other than the one specified in the current URL you have to specify it explicitly. Using an empty BeginForm() will cause the form to post to the same URL that was returned on the previous request.
I believe what you have is the best way to tackle the problem and is the way I have my MVC application implemented as well. There is nothing wrong with being explicit, especially when it comes to views and view logic because they are by their very nature explicit. Separating the different submit buttons into different action methods is a solid approach and one that will inherently require you to specify which action to target for each submit button. You can think of this approach as analogous to Web Forms Server Side Event Handlers for button clicks (minus all the nasty page life cycle). This approach is elegant and clean, only the server side code corresponding to the submit is executed.
Just started converting to MVC from classic ASP and wondering about best practice for if-statements that is used to decide if a HTML-element should be visible or deciding if that or that HTML-element should be visible.
Example:
if (type = 1)
{
<img src="/Content/Images/gray.jpg" />
}
else
{
<img src="/Content/Images/blue.jpg" />
}
I see several options here.
1. Use if-statements in my views (prefer not to)
2. Use a HTML-helper (will be a lot of helpers = Hard to work with the code)
3. Create HTML-snippets in my controller and pass into my view
4. Work with JavaScript to show/hide element on document load.
The example above if of course a simplification of my problem. Many thanks for the help!
/Mike
EDIT:
Took a bad example, here comes a new:
if (type = 1)
{
<span class="class1">Something something</span>
}
else
{
<div id="id1">
text
<img src="/Content/Images/gray.jpg" />
</div>
}
Personally I'm starting to use a separate layer for loading up by viewmodels with html specific information.
public class ViewModel
{
public string ImageName { get; set; }
}
Keeps the views clean:
<img src="/Content/Images/<%= Model.ImageName %>.jpg" />
I personally prefer to use HtmlHelper extensions or declarative HTML helpers for all but most trivial conditional expressions. However in this case I would probably let my laziness win, and express the conditional directly in the view like so:
<img src="/Content/Images/#(i == 1 ? "gray" : "blue").jpg" />
Edit
For more complex scenarios, definitely HTML helpers. If you're using the Razor syntax, you can create declarative helpers by placing them into your ~/Views/Helpers directory. They're very clean to write and use.
But the idea behind creating a good helper is that it encapsulates an intention. This is easy to test by trying to give your helper a name. If the name is IfOneThenShowSomethingElseGrayImage, you should try to break it into further pieces still.
So let's imagine the intention in your example is to show information about the current user based on the user type. You could create a helper like this:
#helper UserType(int type) {
#if (type == 1) {
<span class="class1">Something something</span>
} else {
<div id="id1">
text
<img src="/Content/Images/gray.jpg" />
</div>
}
}
And then use it in your view like this:
#UserType(Model.UserType)
Even though it's only used on one page, it enhances the readability in the view, and can be reused if necessary.
Just my two cents.
I have a lot of content-heavy views in my ASP.NET MVC 2 site. These contain several re-occurring HTML patterns. When using ASP.NET Webforms, a class derived from WebControl could encapsulate these patterns. I'd like some pointers on the correct approach for this problem with MVC.
Detailed Explanation
Patterns not unlike the following HTML markup keep occurring throughout these views. The markup renders into an isolated a box of content:
<div class="top container">
<div class="header">
<p>The title</p>
<em>(and a small note)</em>
</div>
<div class="simpleBox rounded">
<p>This is content.</p>
<p><strong>Some more content</strong></p>
</div>
</div>
This is a trivial example, but there are more complex recurring patterns. In ASP.NET Webforms I would have abstracted such code into a WebControl (let's say I'd have named it BoxControl), being included on a page like this:
<foo:BoxControl runat="server">
<Header>The title</Header>
<Note>(and a small note)</Note>
<Content>
<p>This is content.</p>
<p><strong>Some more content</strong></p>
</Content>
</foo:BoxControl>
This abstraction makes it easy to adapt the way the box is constructed throughout the site, by just altering the BoxControl source. It also keeps the static HTML content neatly together in the View Page, even when combining several BoxControls on a page. Another benefit is that the HTML used as content is recognized by the IDE, thus providing syntax highlighting/checking.
To my understanding, WebControls are discouraged in ASP.NET MVC. Instead of a WebControl, I could accomplish the abstraction with a partial view. Such a view would then be included in a View Page as follows:
<%= Html.Partial("BoxControl", new {
Header="The Title",
Note="(and a small note)",
Content="<p>This is content.</p><p><strong>Some more content</strong></p>"});
%>
This is not ideal, since the 'Content' parameter could become very long, and the IDE does not treat it as HTML when passed this way.
Considered Solutions
Strongly-Typed ViewModels can be passed to the Html.Partial call instead of the lengthy parameters shown above. But then I'd have to pull the content in from somewhere else (a CMS, or Resource file). I'd like for the content to be contained in the View Page.
I have also considered the solution proposed by Jeffrey Palermo, but that would mean lots of extra files scattered around the project. I'd like the textual content of any view to be restricted to one file only.
Should I not want to abstract the markup away? Or is there maybe an approach, suitable for MVC, that I am overlooking here? What is the drawback to 'sinning' by using a WebControl?
There is a solution to this problem, although the way to get there is a little more clutsy than other frameworks like Ruby on Rails.
I've used this method to create markup for Twitter Bootstrap's control group syntax which looks like this:
<div class="control-group">
<label class="control-label">[Label text here]</label>
<div class="controls">
[Arbitrary markup here]
</div>
</div>
Here's how:
1) Create a model for the common markup snippet. The model should write markup on construction and again on dispose:
using System;
using System.Web.Mvc;
namespace My.Name.Space
{
public class ControlGroup : IDisposable
{
private readonly ViewContext m_viewContext;
private readonly TagBuilder m_controlGroup;
private readonly TagBuilder m_controlsDiv;
public ControlGroup(ViewContext viewContext, string labelText)
{
m_viewContext = viewContext;
/*
* <div class="control-group">
* <label class="control-label">Label</label>
* <div class="controls">
* input(s)
* </div>
* </div>
*/
m_controlGroup = new TagBuilder("div");
m_controlGroup.AddCssClass("control-group");
m_viewContext.Writer.Write(m_controlGroup.ToString(TagRenderMode.StartTag));
if (labelText != null)
{
var label = new TagBuilder("label");
label.AddCssClass("control-label");
label.InnerHtml = labelText;
m_viewContext.Writer.Write(label.ToString());
}
m_controlsDiv = new TagBuilder("div");
m_controlsDiv.AddCssClass("controls");
m_viewContext.Writer.Write(m_controlsDiv.ToString(TagRenderMode.StartTag));
}
public void Dispose()
{
m_viewContext.Writer.Write(m_controlsDiv.ToString(TagRenderMode.EndTag));
m_viewContext.Writer.Write(m_controlGroup.ToString(TagRenderMode.EndTag));
}
}
}
2) Create a nifty Html helper
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using My.Name.Space
namespace Some.Name.Space
{
public static class FormsHelper
{
public static ControlGroup ControlGroup(this HtmlHelper helper, string labelText)
{
return new ControlGroup(helper.ViewContext, labelText);
}
}
}
3) Use it in the view (Razor code)
#using (Html.ControlGroup("My label"))
{
<input type="text" />
<p>Arbitrary markup</p>
<input type="text" name="moreInputFields" />
}
This is also the way MVC framework renders a form with the Html.BeginForm method
Well you wouldn't render the partial like that, pass it a strongly-typed ViewModel, like this:
<%= Html.RenderPartial("BoxControl", contentModel) %>
contentModel is the ViewModel (just a POCO-like storage mechanism for your views), which the strongly typed partial view would bind to.
So you can do this in your partial view:
<h1><%: Model.Header %></h1>
<p><%: Model.Content %></p>
etc etc
After considering the answers and running an experiment, I'm inclined to adhere to the pure MVC approach and duplicate some presentation code throughout View Pages. I'd like to elaborate on the rationale for that decision.
Partial View
When using a Partial View, The content for the box needs to be passed as a View Model, making the View Page less readable versus declaring the content HTML on the spot. Remember that the content does not come from a CMS, so that would mean filling the View Model with HTML in a controller or setting a local variable in the View Page. Both of these methods fail to take advantage of IDE features for dealing with HTML.
WebControl
On the other hand, a WebControl-derived class is discouraged and also turns out to have some practical issues. The main issue that the declarative, hierarchical style of traditional ASP.NET .aspx pages just does not fit the procedural style of MVC.NET View Pages. You have to choose for either a full blown traditional approach, or go completely MVC.
To illustrate this, the most prominent issue in my experimental implementation was one of variable scope: when iterating a list of products, the MVC-way is to use a foreach loop, but that introduces a local variable which will not be available in the scope of the WebControl. The traditional ASP.NET approach would be to use a Repeater instead of the foreach. It seems to be a slippery slope to use any traditional ASP.NET controls at all, because I suspect you'll soon find yourself needing to combine more and more of them to get the job done.
Plain HTML
Forgoing the abstraction at all, you are left with duplicate presentation code. This is against DRY, but produces very readable code.
It doesnt look like webforms has that much less html to me, it seems more like a lateral move. Using a partial in MVC can make it cleaner but the html markup you needed will still be there, in one place or another. If its mostly the extra html that bothers you, you might take a look at the NHaml view engine or check out haml
the haml website.
I'm by no means a Haml expert but the html does look a lot cleaner...
.top container
.header
%p
The title
%em
(and a small note)
.simpleBox rounded
%p
This is content.
%p
Some more content