DRY in the MVC View - asp.net-mvc

I've been working a lot with asp.net web forms and one think that I like about the is the consistency with the generated markup e.g. if you create a composite control for a TextField you can control the generated markup in a single class like and don't break the SRP:
<form:textfield id="firstName" runat="server" required="true" label="First Name" />
I you're your going to generate the markup by hand it might look like this:
<label for="firstName" id="lbl_firstName">Name <span class="required">*</span></label>
<input id="firstName" name="firstName" type="text" value="" />
The problem is when would like to change something for example add a wrapping div or move the span. In worst case you have to edit thousands of views.
That's why I really like the MVC Contrib FluentHtml.
<%= this.TextBox(x => x.Message.PostedBy).Class("required").Label("Name") %>
My question is what do you think is the best way to add a wrapping div for the code line above? I think hand writing is not an option because of the arguments above? Perhaps extending the TextBox : MvcContrib.FluentHtml.Elements.TextInput?

have you checked InputBuilder in MvcContrib project? it is used in Codecampserver as well. have a look and i think u will like it.

Honestly, I don't think the example case you've given applies to real world. A textbox is a textbox. If you need one, you render one.
If you need a more "complex" control like a textbox wrapped in a div tag, then you can have a partial view for that.
For example, Model :
public class CustomControlModel {
public string Name { get; set; }
public string Value { get; set; }
public string Class { get; set; }
public bool WrapInDivTag { get; set; }
//you get the idea
}
Custom Control :
<%# Control Inherits="System.Web.Mvc.ViewUserControl<CustomControlModel>" %>
<%if (Model.WrapInDivTag) {%> <div> <% } %>
<%=Html.TextBox(Model.Name, Model.Value, new { #class = Model.Class })%>
<%if (Model.WrapInDivTag) {%> </div> <% } %>
And when rendering :
<%Html.RenderPartial("CustomControl",
new CustomControlModel { Name = "name", WrapInDivTag = true }); %>
That's a very simple example but I hope it explains why I suggested partial views. Don't forget that you can expose another property to get which tag to render etc.

InputBuilders are one option. With FluentHtml you could create a custom element, something like this:
public class TextBoxInContainer : TextInput<TextBox>
{
public TextBoxInContainer (string name) : base(HtmlInputType.Text, name) { }
public TextBoxInContainer (string name, MemberExpression forMember, IEnumerable<IBehaviorMarker> behaviors) : base(HtmlInputType.Text, name, forMember, behaviors) { }
protected override ToString()
{
divBuilder = new TagBuilder(HtmlTag.Div);
divBuilder.InnerHtml = ToString();
return divBuilder.ToString(TagRenderMode.SelfClosing);
}
}
To use this from your view you would extend IViewModelContainer something like this:
public static MyTextBox TextBoxInContainer <T>(this IViewModelContainer<T> view, Expression<Func<T, object>> expression) where T : class
{
return new TextBoxInContainer (expression.GetNameFor(view), expression.GetMemberExpression(), view.Behaviors)
.Value(expression.GetValueFrom(view.ViewModel));
}
Then if you want to change your container to a span sitewide, you change the ToString method of TextBoxInContainer.

Related

Pass a child component as Parameter in Blazor

I would like to do in Blazor something that I normally do in React: create a reusable component that internally uses other child components, with the ability to pass those child components as parameters. I need that to be able to treat child components as a dependency that can be injected on demand with any custom implementation that could be needed in different contexts.
Imagine, for instance, a TextBox.razor component that gives you the ability to pass a custom component to render the label as you want, as long as it implements an ILabel interface. I tried something like this but the syntax doesn't seem to be valid:
TextBox.razor
As you see from the screenshot, Blazor doesn't allow me to use the Parameter Label as a component. Any idea of how to achieve this?
You should be able to accomplish this with templated components.
Textbox.razor
#typeparam inputType
<div class="textbox">
#if(LabelTemplate!=null && TItem!=null)
#LabelTemplate(TItem)
<input type="text"/>
</div>
#code{
[Parameter]
public RenderFragment<inputType> LabelTemplate { get; set; }
[Parameter]
public inputType TItem { get; set; }
}
In the code above, you are specifying that the component accepts a type using #typeparam inputType and receive an object of that type as a parameter TItem.
You are also accepting a LabelTemplate which accepts an object of type inputType. To render this fragment, we call #LabelTemplate and pass in our TItem parameter.
Now lets look at how to use our templated component in a new component called PersonForm.razor
PersonForm.razor
<Textbox TItem="myPerson">
<LabelTemplate>
#context.Name
</LabelTemplate>
</Textbox>
<Textbox TItem="myPerson">
<LabelTemplate>
#context.PhoneNumber
</LabelTemplate>
</Textbox>
#code{
Person myPerson = new Person { Name = "Jane Doe", PhoneNumber = "999 999 9999" };
public class Person
{
public string Name { get; set; }
public string PhoneNumber { get; set; }
}
}
I'm passing in my Person object to each Textbox component's TItem property, and accessing it in the LabelTemplate using the #context syntax.
This might seem confusing at first, so please read up on it here
Edited
It just depends on what you want to accomplish. With the Verbose syntax comes flexibility on the "implementation" side of the component. Instead of forcing an interface that might not work with a wide variety of models/classes, you are letting the implementation specify what to do.
If you want something less verbose/more rigid, you can do the following as well.
#implements ILabel
<div class="textbox">
<label>#Text</label>
<input type="text"/>
</div>
#code
{
[Parameter]
public string Text { get; set; }
}
ILabel.cs
public interface ILabel
{
string Text { get; set; }
}
I realize this is probably late, but I just struggled through this and found out it is SUPER easy! Thought I would put an easy answer out there for people looking.
Here is my OrdersNavigation.razor file (which I want to embed into a header):
<div class="nav-strip">
<NavLink href="orders">
<Icon Name="#Icons.Cart" /> List
</NavLink>
<NavLink href="orders/create">
<Icon Name="#Icons.Plus" /> Create
</NavLink>
</div>
Now here is my PageHeader.razor:
<div class="page-header">
<h3>#Title</h3>
#Navigation
</h3>
<hr />
#code {
[Parameter] public string Title { get; set; } = "[TITLE]";
[Parameter] public RenderFragment Navigation { get; set; }
}
Notice that the Navigation property is a RenderFragment - this is key. Now in my page, I can simply add it like this:
<PageHeader Title="Orders">
<Navigation>
<OrderNavigation />
</Navigation>
</PageHeader>
You see here that the Title parameter is entered like usual, but the Navigation parameter is entered as an element of PageHeader! Really, you can put anything in the tags and it will render where you have #Navigation.
Reference: https://blazor-university.com/templating-components-with-renderfragements/passing-data-to-a-renderfragement/
Took a shot at your example:
Label.razor
<label>#Text</label>
#code {
[Parameter] public RenderFragment Text { get; set; }
}
TextBox.razor
<div class="textbox">
<Label>
<Text>
<div>
Embedded label <br />
You can even drop components in here!
</div>
</Text>
</Label>
<input />
</div>

How to get sequence/array index in Editor Template?

Case:
I have a list of items of Class X displayed using Editor Template for Class X.
Problem:
How can I get index of an item being processed on the inside of the Editor Template?
I've been using this HtmlExtension that returns only the needed id of an iteration. It's basically a regex on ViewData.TemplateInfo.HtmlFieldPrefix that's capturing the last number.
public static class HtmlExtensions
public static MvcHtmlString Index(this HtmlHelper html)
{
var prefix = html.ViewData.TemplateInfo.HtmlFieldPrefix;
var m = Regex.Match(prefix, #".+\[(\d+)\]");
if (m.Success && m.Groups.Count == 2)
return MvcHtmlString.Create(m.Groups[1].Value);
return null;
}
}
Can be used in an EditorFor-template like this:
#Html.Index()
Use a for loop instead of for each and pass the indexer into the EditorFor extension; razor should handle the rest.
#for(var i = 0; i < Model.count(); i++)
{
#Html.EditorFor(m => Model.ToArray()[i], new { index = i })
}
Update:
pass in the the index of the item using view data as show above.
In your editor template access the item via the ViewBag
<span> Item Index: #ViewBag.index </span>
Using the EditorTemplate is the best solution when viewing models that contain a list of something.
In order to find the index for the sub-model being rendered you can use the property that Razor sets by default:
ViewData.TemplateInfo.HtmlFieldPrefix
Say, for example, you have the following view models:
public class ParagraphVM
{
public int ParagraphId { get; set; }
public List<LineVM> Lines { get; set; }
}
and
public class LineVM
{
public int Id { get; set; }
public string Text {get; set;}
}
and you want to be able to edit all the "LineVM" within a "ParagraphVM". Then you would use an Editor Template so you would create a view at the following folder (if it doesn't exist) with the same name as the sub-model Views/Shared/EditorTemplates/LineVM.cshtml:
#model MyProject.Web.MVC.ViewModels.Paragraphs.LineVM
#{
//this will give you the List's element like Lines[index_number]
var field = ViewData.TemplateInfo.HtmlFieldPrefix;
}
<div id="#field">
#Html.EditorFor(l => l.Text)
</div>
Assuming you have a Controller's ActionResult that is returning a View and passing a ParagrapghVM viewmodel to a view, for example Views/Paragraph/_Paragraph.cshtml:
#model MyProject.Web.MVC.ViewModels.Paragraphs.ParagraphVM
#using (Html.BeginForm("Details", "Paragraphs", FormMethod.Post))
{
#Html.EditorFor(p => p.Lines)
}
This view would render as many editors for the list Lines as items contains that list.
So if, for example, the property list ParagraphVM.Lines contains 3 items it would render something like:
<div id="#Lines[0]">
<input id="Lines_0__Text name="Lines[0].Text"/>
</div>
<div id="#Lines[1]">
<input id="Lines_1__Text name="Lines[1].Text"/>
</div>
<div id="#Lines[2]">
<input id="Lines_2__Text name="Lines[2].Text"/>
</div>
With that you can know exactly what position each items is within the list and for example use some javascript to create a carousel or whatever you want to do with it. But remember that to edit that list you don't really need to know the position as Razor takes care of it for you. If you post back the model ParagraphVM, the list Lines will have the values bound (if any) without any additional work.
How about:
#using System
#using System.Text.RegularExpressions
var i = Convert.ToInt32(Regex.Matches(
ViewData.TemplateInfo.HtmlFieldPrefix,
#"\[([0-9]+)?\]")[0].Groups[1].ToString());
I think the easiest way is:
#Regex.Match(ViewData.TemplateInfo.HtmlFieldPrefix, #"(?!\[)\d+(?=\])")
Or as helper:
public static string Index(this HtmlHelper html)
{
Match m = Regex.Match(html.ViewData.TemplateInfo.HtmlFieldPrefix, #"(?!\[)\d+(?=\])");
return m.Success ? m.Value : null;
}
Inspired by #Jona and #Ryan Penfold
You can use #Html.NameFor(m => m.AnyField). That expression will output the full name property including the index. You could extract the index there...

MVC3 using CheckBox with a complex viewmodel

Right guys. I need your brains as I can't find a way to do this properly.
I have a view model:
public class EditUserViewModel
{
public User User;
public IQueryable<ServiceLicense> ServiceLicenses;
}
User is unimportant as I know how to deal with it.
ServiceLicenses has the following implementation:
public class ServiceLicense
{
public Guid ServiceId { get; set; }
public string ServiceName { get; set; }
public bool GotLic { get; set; }
}
Getting a checked list of users is cool. It works like a charm.
<fieldset>
<legend>Licenses</legend>
#foreach (var service in Model.ServiceLicenses)
{
<p>
#Html.CheckBoxFor(x => service.GotLic)
#service.ServiceName
</p>
}
</fieldset>
The problem I'm having is getting the updated ServiceLicenses object with new checked services back to the HttpPost in my controller. For simplicity lets say it looks like this:
[HttpPost]
public ActionResult EditUser(Guid id, FormCollection collection)
{
var userModel = new EditUserViewModel(id);
if (TryUpdateModel(userModel))
{
//This is fine and I know what to do with this
var editUser = userModel.User;
//This does not update
var serviceLicenses = userModel.ServiceLicenses;
return RedirectToAction("Details", new { id = editUser.ClientId });
}
else
{
return View(userModel);
}
}
I know I am using CheckBox the wrong way. What do I need to change to get serviceLicenses to update with the boxes checked in the form?
i understand that ServiceLicenses property is a collection and you want MVC binder to bind it to you action parameters property. for that you should have indices attached with inputs in your view e.g
<input type="checkbox" name = "ServiceLicenses[0].GotLic" value="true"/>
<input type="checkbox" name = "ServiceLicenses[1].GotLic" value="true"/>
<input type="checkbox" name = "ServiceLicenses[2].GotLic" value="true"/>
Prefix may not be mandatory but it is very handy when binding collection property of action method parameter. for that purpose i would suggest using for loop instead of foreach and using Html.CheckBox helper instead of Html.CheckBoxFor
<fieldset>
<legend>Licenses</legend>
#for (int i=0;i<Model.ServiceLicenses.Count;i++)
{
<p>
#Html.CheckBox("ServiceLicenses["+i+"].GotLic",ServiceLicenses[i].GotLic)
#Html.CheckBox("ServiceLicenses["+i+"].ServiceName",ServiceLicenses[i].ServiceName)//you would want to bind name of service in case model is invalid you can pass on same model to view
#service.ServiceName
</p>
}
</fieldset>
Not using strongly typed helper is just a personal preference here. if you do not want to index your inputs like this you can also have a look at this great post by steve senderson
Edit: i have blogged about creating master detail form on asp.net mvc3 which is relevant in case of list binding as well.

HttpPostedFileBase editor for using with EditorForModel

Okay, maybe I'm missing something, but I can't figure this out. Using ASP.NET MVC 3, Razor views.
I have a model object like this:
public class MyModel
{
public HttpPostedFileBase File { get; set; }
public string Title { get;set; }
public string Description { get; set; }
}
When, in a strongly typed view, I call #Html.EditorForModel(), it only generates the Title and Description form fields.
I created the file: Views\Shared\EditorTemplates\HttpPostedFileBase.cshtml, with dummy content, but it still doesn't get rendered.
Is it possible to get EditorForModel to generate file input fields?
I managed to get it working by creating a custom Object.cshtml editor template:
#foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForEdit && !ViewData.TemplateInfo.Visited(pm)))
{
if (prop.HideSurroundingHtml)
{
#Html.Editor(prop.PropertyName)
}
else
{
<div class="editor-container">
<div class="editor-label">
#Html.Label(prop.PropertyName, prop.DisplayName)
</div>
<div class="editor-field">
#Html.Editor(prop.PropertyName, prop.TemplateHint)
#Html.ValidationMessage(prop.PropertyName, "*")
</div>
</div>
}
}
Basically it calls Html.Editor() for each property of the model. I don't know if it's a good solution, but it works for now.
I have investigated a similar problem - the editor for complex properties is not output. Your case may be different but the workaround that I found (creating an editor for your model - Model.ascx) should work for you too.

How can I validate the result in an ASP.NET MVC editor template?

I have created an editor template for representing selecting from a dynamic dropdown list and it works as it should except for validation, which I have been unable to figure out. If the model has the [Required] attribute set, I want that to invalidate if the default option is selected.
The view model object that must be represented as the dropdown list is Selector:
public class Selector
{
public int SelectedId { get; set; }
public IEnumerable<Pair<int, string>> Choices { get; private set; }
public string DefaultValue { get; set; }
public Selector()
{
//For binding the object on Post
}
public Selector(IEnumerable<Pair<int, string>> choices, string defaultValue)
{
DefaultValue = defaultValue;
Choices = choices;
}
}
The editor template looks like this:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<select class="template-selector" id="<%= ViewData.ModelMetadata.PropertyName %>.SelectedId" name="<%= ViewData.ModelMetadata.PropertyName %>.SelectedId">
<%
var model = ViewData.ModelMetadata.Model as QASW.Web.Mvc.Selector;
if (model != null)
{
%>
<option><%= model.DefaultValue %></option><%
foreach (var choice in model.Choices)
{
%>
<option value="<%= choice.Value1 %>"><%= choice.Value2 %></option><%
}
}
%>
</select>
I sort of got it to work by calling it from the view like this (where Category is a Selector):
<%= Html.ValidationMessageFor(n => n.Category.SelectedId)%>
But it shows the validation error for not supplying a proper number and it does not care if I set the Required attribute.
I found a solution where validation is done against hidden fields using custom validation rules, here. Using this approach you can easily add custom validation to arbitrary types.
Why is not your editor template strongly typed?
<%# Control Language="C#"
Inherits="System.Web.Mvc.ViewUserControl<QASW.Web.Mvc.Selector>" %>
Why not use the DropDownListFor helper:
<%= Html.DropDownListFor(
x => x.SelectedId,
new SelectList(Model.Choices, "Value1", "Value2")
)%>
To avoid the magic strings you could add a ChoicesList property to your view model:
public IEnumerable<SelectListItem> ChoicesList
{
get
{
return Choices.Select(x => new SelectListItem
{
Value = x.Value1.ToString(),
Text = x.Value2
});
}
}
and bind your helper to it:
<%= Html.DropDownListFor(x => x.SelectedId, Model.ChoicesList) %>

Resources