Can somebody please suggest how I could write a Unit Test with Moq for following HtmlHelper method?
public static HtmlTagBase GenerateTag<T>(this HtmlHelper htmlHelper
, object elementData
, object attributes)
where T : HtmlTagBase
{
return (T)Activator.CreateInstance(typeof(T)
, htmlHelper.ViewContext
, elementData
, attributes);
}
which you would use as follows (please note the using statement - this is causing me confusion):
<%--Model is a type of ShareClass--%>
<% using (Html.GenerateTag<DivTag>(Model)) { %>
My Div
<% } %>
using this method, if you specify T as type DivTag, where ShareClass is defined as
public class ShareClass
{
public string Name { get; set; }
public string Type { get; set; }
public IEnumerable<Fund> Funds { get; set; }
public ShareClass(string name, string shareClassType)
{
this.Name = name;
this.Type = shareClassType;
}
}
the following html will be rendered:
<div class="ShareClass" shareclass-type="ShareClass_A" shareclass-name="MyShareClass">My Div</div>
I think this 3 links will help
Prepare Html Helper https://stackoverflow.com/a/747186/980003
Create test method https://stackoverflow.com/a/3424771/980003 (but use HtmlHelper from first link)
Assert result with you expectations as normal.
Here is example of disposable helper. How it looks like inside. https://stackoverflow.com/a/8292524/980003
Related
I have a database with folders where I store the whole path to a folder as a folder_name so sth. like: //MainFolder/subFolder/folderName. Im my application I have a Model called Folder, which represents the folders from db.
Model:
public class Folder
{
[Key]
public string folder_name { get; set; }
[NotMapped]
public string folder_name_short
{
get
{
string shortname = folder_name.Substring(folder_name.LastIndexOf("/"), folder_name.Length - folder_name.LastIndexOf("/")); //System.NullReferenceException here
return shortname;
}
set
{
string shortname = folder_name.Substring(folder_name.LastIndexOf("/"), folder_name.Length - folder_name.LastIndexOf("/"));
this.folder_name = folder_name.Replace(shortname, value);
}
}
}
folder_name_short isn't mapped because it is only a substring from the whole path, so I don't want to store it twice. Example want I mean:
Console.WriteLine(folder_name) output://MainFolder/subFolder/folderName
Console.WriteLine(folder_name_short) output:/folderName
In my View the user can rename the folder. So I would like to replace the old folder_name_short to the new one and store the new String in folder_name
View:
.
.
using (Html.BeginForm("Rename", "Folders", FormMethod.Post))
{
#Html.EditorFor(model => model.folder_name_short)
<input type="submit" value="rename folder" id="submit" class="btn btn-default" />
}
.
.
Problem:
The Inputtextbox renders and shows the current Value of folder_name_short in it. When I change it and click the submit button, I get a System.NullReferenceException in the Model (as marked in the source code, please scroll to right). I don't understand what is wrong and what changes are needed.
Edit:
when the setter is commented out, the Exception disappear. So maybe the setter is causing the error?
Solution:
use the standard setter and getter for storing the folder_name_shortvalue, but implement a public get/set method to set the folder_name in the db and call this method in the controller. So:
[NotMapped]
public string folder_name_short { get; set; }
public string getfolder_name_short()
{
string shortname = folder_name.Substring(folder_name.LastIndexOf("/"), folder_name.Length - folder_name.LastIndexOf("/"));
return shortname;
}
public void setfolder_name_short(string newname)
{
string shortname = folder_name.Substring(folder_name.LastIndexOf("/"), folder_name.Length - folder_name.LastIndexOf("/"));
folder_name = folder_name.Replace(shortname, newname);
}
If the value that is edited on the form is the short name folder_name_short then the model class could contain its string property:
public string folder_name_short { get; set; }
This property does not contain any logic. Any other code that contains some logic could be in the controller in an action method.
It seems you are using the same class for DB access and as a model class for the MVC view, which is the source of the problem.
[HttpPost]
[("Rename")]
public async Task<IHttpActionResult> ChangeFolderPath(Folder folder)
{
//Put a breakpoint here and make sure the folder has a value
}
probably that the setter method overwrite the value of folder_name.
I would recommand this thread to help understand custom setters
C# properties: how to use custom set property without private field?
EDIT
public class Folder
{
[Key]
public string folder_name { get; set; }
/*Model Logic shouldn't be in the Poco*/
/*You may put that in your model or best thing would be to implement a repository pattern*/
public string ShortName()
{
string shortname = folder_name.Substring(folder_name.LastIndexOf("/"), folder_name.Length - folder_name.LastIndexOf("/")); //System.NullReferenceException here
return shortname;
}
public void SetUsingShortName(string value)
{
string shortname = folder_name.Substring(folder_name.LastIndexOf("/"), folder_name.Length - folder_name.LastIndexOf("/"));
this.folder_name = folder_name.Replace(shortname, value);
}
}
I want DRY/reuse as much editor code (View and Model) as possible. Some of my fields can only be set at creation, and never edited. Are there any pre-existing MVC/DataAnnotation features I should look at?
For example, maybe there is a data attribute that causes EditorFor to operate like DisplayFor if the value is non-null.
Model.cs
[Unchangeable]
string UserReferenceId { get; set; }
string Description { get; set; }
edit: to clarify my goal, I've added an answer with sample code for the approach I'm currently planning. If there's a better way/pre-existing feature for this, please let me know.
There are both the System.ComponentModel.ReadOnlyAttribute and System.ComponentModel.DataAnnotations.EditableAttribute (I think EditableAttribute is .NET 4). When model metadata is created for properties marked with either of these, you can see ModelMetadata.IsReadOnly will be set correctly.
Frustratingly, however, the built-in editor templates will still show editable fields, even if ModelMetadata.IsReadOnly is true.
You can, however, create your own shared editor template for each data type where you want this metadata property respected, and handle it specifically.
~/Views/Shared/EditorTemplates/String.cshtml
#model String
#if (ViewData.ModelMetadata.IsReadOnly)
{
#Html.Hidden(string.Empty, Model)
}
#(ViewData.ModelMetadata.IsReadOnly ? Html.DisplayText(string.Empty) : Html.TextBox(string.Empty))
View Model
[Editable(false)]
public string UserReferenceId { get; set; }
You'll note that in the event the metadata for the model indicates IsReadOnly, I draw a hidden field. This is so the value of that property is persisted across posts.
If you don't want the field displayed at all, but persisted across posts, you can use System.Web.Mvc.HiddenInputAttribute. In this case, only the hidden is drawn.
[HiddenInput(DisplayValue=false)]
public string UserReferenceId { get; set; }
Here's what I'm thinking of implementing if nothing similar is pre-existing:
EditableWhenNewModel.cs
public class EditableWhenNewModel : IIsNew
{
public bool IsNew { get { return recordId == 0; } }
string UserReferenceId { get; set; }
string Description { get; set; }
public void Save(RepositoryItem record) {
if (IsNew) { record.UserReferenceId = UserReferenceId; }
record.Description = Description;
... etc.
View.cshtml
#model EditableWhenNewModel
#Html.EditorWhenNewFor(m => m.UserReferenceId)
#Html.EditorFor(m => m.Description)
EditorWhenNewFor.cs
public static MvcHtmlString EditorWhenNewFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression
) where TModel : IIsNew {
return htmlHelper.ViewData.Model.IsNew ?
htmlHelper.EditorFor(expression) :
htmlHelper.DisplayFor(expression);
}
I have the following classes:
public class Note
{
public string Text { get; set; }
public RowInfo RowInfo { get; set; }
}
public class RowInfo
{
[DisplayName("Created")]
public DateTime Created { get; set; }
[DisplayName("Modified")]
public DateTime Modified { get; set; }
}
In my view I have the following which creates HTML with the correct name and value:
Html.HiddenFor(model => model.Note.Created)
Now what I am trying to do is to create an extension method that will include the above and that I can call in each view. I have tried doing the following. I think I am on the right track but I don't know how to do the equivalent of "model => model.Note.Created" Can someone give me some advice on how I can do this and what I would need to replace the text inside the parenthesis with. I don't have a model but I can do this some other way so the hidden field will go look at my class to get the correct DisplayName just like it does above?
namespace ST.WebUx.Helpers.Html
{
using System.Web.Mvc;
using System.Web.Mvc.Html
using System.Linq;
public static class StatusExtensions
{
public static MvcHtmlString StatusBox(this HtmlHelper helper, RowInfo RowInfo )
{
return new MvcHtmlString(
"Some things here ... " +
System.Web.Mvc.Html.InputExtensions.Hidden( for created field ) +
System.Web.Mvc.Html.InputExtensions.Hidden( for modified field ) );
}
}
You could write a strongly typed helper taking a λ-expression:
public static class StatusExtensions
{
public static IHtmlString StatusBox<TModel, TProperty>(
this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> ex
)
{
return new HtmlString(
"Some things here ... " +
helper.HiddenFor(ex));
}
}
and then:
#Html.StatusBox(model => model.RowInfo.Created)
UPDATE:
As requested in the comments section here's a revised version of the helper:
public static class StatusExtensions
{
public static IHtmlString StatusBox<TModel>(
this HtmlHelper<TModel> helper,
Expression<Func<TModel, RowInfo>> ex
)
{
var createdEx =
Expression.Lambda<Func<TModel, DateTime>>(
Expression.Property(ex.Body, "Created"),
ex.Parameters
);
var modifiedEx =
Expression.Lambda<Func<TModel, DateTime>>(
Expression.Property(ex.Body, "Modified"),
ex.Parameters
);
return new HtmlString(
"Some things here ..." +
helper.HiddenFor(createdEx) +
helper.HiddenFor(modifiedEx)
);
}
}
and then:
#Html.StatusBox(model => model.RowInfo)
Needless to say that custom HTML helpers should be used to generate small portions of HTML. Complexity could grow quickly and in this case I would recommend you using an editor template for the RowInfo type.
Is it a code smell to have to following pattern, given the following code (highly simplified to get straight to the point) ?
The models :
class Product
{
public int Id { get; set; }
public string Name { get; set; }
public Category Cat { get; set; }
}
class Category
{
public int Id { get; set; }
public string Label { get; set; }
}
The view to edit a Product :
<% =Html.EditorFor( x => x.Name ) %>
<% =Html.EditorFor( x => x.Category ) %>
The EditorTemplate for Category
<% =Html.DropDownList<Category>() %>
The HtmlHelper method
public static MvcHtmlString DropDownList<TEntity>(this HtmlHelper helper)
where TEntity : Entity
{
var selectList = new SelectList(
ServiceLocator.GetInstance<SomethingGivingMe<TEntity>>().GetAll(),
"Id", "Label");
return SelectExtensions.DropDownList(helper, "List", selectList, null, null);
}
For information, the real implementation of the helper method takes some lambdas to get the DataTextField and DataValueField names, the selected value, etc.
The point that bothers me is using a servicelocator inside the HtmlHelper. I think I should have a AllCategories property in my Product model, but I would need to be populated in the controller every time I need it.
So I think the solution I'm using is more straightforward, as the helper method is generic (and so is the modelbinder, not included here). So I just have to create an EditorTemplate for each type that needs a DropDownList.
Any advice ?
IMHO I'd leave it the way it is, have the same thing in another project.
BUT the service location bothered me as well so for another project I made this part of an ActionFilter which scans a model, finds all the anticipated dropdowns and does a batch load into ViewData. Since the ServiceLocator or Repository/Context/whatever is already injected into the Controller you don't have to spread your service location all over the place.
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
foreach( var anticipated in SomeDetectionMethod() )
{
var selectList = new SelectList(
ServiceLocator.GetInstance<SomethingGivingMe<TEntity>>().GetAll(),
"Id", "Label");
ViewData["SelectList." + anticipated.Label/Name/Description"] = selectList;
}
}
In the view you can then make a helper to load up those dropdowns via a custom editor template or other method.
advice: look at the asp.net mvc sample application from here: http://valueinjecter.codeplex.com/
good luck ;)
This is how ValueInjecter's Sample Application could get the dropdowns:
(but it doesn't right now cuz I'm ok with the Resolve thing)
public class CountryToLookup : LoopValueInjection<Country, object>
{
ICountryRepo _repo;
public CountryToLookup(ICountryRepository repo)
{
_repo = repo;
}
protected override object SetValue(Country sourcePropertyValue)
{
var value = sourcePropertyValue ?? new Country();
var countries = _repo.GetAll().ToArray();
return
countries.Select(
o => new SelectListItem
{
Text = o.Name,
Value = o.Id.ToString(),
Selected = value.Id == o.Id
});
}
}
Imagine I have a list of objects that implement an interface called ISummary
The objects within this list MAY have additional properties ie.
public interface ISummary {
Guid Id {get;set;}
string Title {get;set;}
DateTime Created {get;set;}
}
public class GigSummary: ISummary {
Guid Id {get;set;}
string Title {get;set;}
DateTime Created {get;set;}
string VenueName {get;set}
string Band {get;set;}
}
public class NewsSummary: ISummary {
Guid Id {get;set;}
string Title {get;set;}
DateTime Created {get;set;}
string Author{get;set}
}
I now pass this list of Gigs and News Summary objects (as a list of ISummary) to the view as the model.
I want to render this list using a different partial for each type contained in the list.
How can I do this is ASP.NET MVC?
The most obvious way I can think of would be to do something like:
foreach(ISummary summ in listOfISummary) {
Html.RenderPartial(String.Fomat("~/Views/Shared/{0}Renderer.ascx", summ.GetType.ToString()), summ, ViewData);%>
}
and create a strongly typed view with a naming convention, like NewsSummaryRenderer.ascx.
I expect that you could move this out to a helper method though, but I'd add it to one of the existing helpers through an extension method rather than putting it in a code behind as suggested previously.
You could put a helper method in the view's codebehind, and then do something like:
Type modelType = this.Model.GetType();
if (modelType == typeof(NewsSummary)) this.RenderPartial("newspartial", this.Model as NewsSummary);
else if (modelType == typeof(GigSummary)) this.RenderPartial("gigpartial", this.Model as GigSummary);
Lewis is on the right track. I would take a slightly different tack--have both of the "widgets" extend from a common base class which provided information about the view names involved. Then add an extension method to your page class to "render widget" which could get the appropriate view in place.
Check out the Kona ASP.NET MVC sample app for a working example of this concept.
I'd create an HtmlHelper extension that did this. Here's some pseudocode that looks shockingly like c# and may actually work:
public static void TemplatedList<T>(this HtmlHelper me, IEnumerable<T> items,
IDictionary<Type, Action<T>> templates)
{
foreach(var item in items)
{
var template = templates[item.GetType()];
if(template != null) template(item);
}
}
I'd use it like this:
<% HtmlHelper.TemplatedList(ViewData.Model, new Dictionary
{
{typeof(GigSummary), x => %>
<div class="gigSummary">
SUP! GIG ANNOUNCEMENT FOR <%= x.Band %>!!
What: <%= x.Title %>
When: <%= x.Created %>
Who: <%= x.Author %>
</div>
<%}
// add more type/template pairs here
}); %>
Here's a simple extension method you can create to extract just the types you need:
public static class Extensions
{
public static IEnumerable<U> ExtractOfType<U, T>(this IEnumerable<T> list)
where T : class
where U : class
{
foreach (var item in list)
{
if (typeof(U).IsAssignableFrom(item.GetType()))
{
yield return item as U;
}
}
}
}
Test:
public interface IBaseInterface
{
string Foo { get; }
}
public interface IChildInterface : IBaseInterface
{
string Foo2 { get; }
}
public interface IOtherChildIntreface : IBaseInterface
{
string OtherFoo { get; }
}
public class BaseImplementation : IBaseInterface
{
public string Foo { get { return "Foo"; } }
}
public class ChildImplementation : IChildInterface
{
public string Foo2 { get { return "Foo2"; } }
public string Foo { get { return "Foo"; } }
}
public class OtherChildImplementation : IOtherChildIntreface
{
public string OtherFoo { get { return "OtherFoo"; } }
public string Foo { get { return "Foo"; } }
}
....
List<IBaseInterface> b = new List<IBaseInterface>();
b.Add(new BaseImplementation());
b.Add(new ChildImplementation());
b.Add(new OtherChildImplementation());
b.Add(new OtherChildImplementation());
foreach (var s in b.ExtractOfType<IOtherChildIntreface, IBaseInterface>())
{
Console.WriteLine(s.GetType().Name);
}
This will get all of the items in the list that are of the derived type you're looking for. So, in your controller, pass in the entire list to the view. Then, have partial views that take IEnumerable's of the type that partial needs, and within your main view, call this extension method and pass on the result to those individual partial views.