I have a Model with properties like this:
[DataType(DataType.MultilineText)]
[MaxLength(512)]
public string Description
{
get
{
this.OnReadingDescription(ref _description);
return _description;
}
set
{
this.OnDescriptionChanging(ref value);
this._description = value;
this.OnDescriptionChanged();
}
}
and a View like this:
#using AspMvcBase.Bootstrap
#model NpoDb.Presentation.Web.Models.Media
#Html.EditForm();
EditForm is a custom HtmlHelper extension which renders every property in the ViewModel.
This part works realy fine but I want do render some of the properties different so I added the [DataType(DataType.MultilineText)] Attribute.
I want to test now in my HtmlHelper if there is an Attribute for a MultilineText.
modelMetadata.DataTypeName.Equals(DataType.EmailAddress.ToString())
But the problem is that DataTypeName is null. Even if I look at it in the View it's null.
This is one of the better resources I found about this.Brad Wilson MVC2 Template
But I can't figure out why it doesn't work in my case.
So the basic question is: How can I access Attributes in a HtmlHelper?
I would really appreciate some help. :)
If you are trying to get the attribute for a property of your model, you can do this:
public static string EditForm(this HtmlHelper helper)
{
var model = helper.ViewData.Model;
var dataType = model
.GetType()
.GetProperty("Description")
.GetCustomAttribute<DataTypeAttribute>()
.DataType;
...
}
Related
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>");
}
So I've got a partial view that displays a model containing some info for a person at an organization. One of the properties is a list of titles which has a UIHint attribute on it to determine what display template to use for it.
Lets say that the model looks like this:
public class Info
{
[UIHint("Titles")]
[DataType("Titles")]
public virtual IEnumerable<string> Titles { get; set; }
}
Let's say that the template for Info looks like:
#model Info
#Html.DisplayFor(x=> x.Titles)
Now we have a very specific type of person-at-org instance that we want to display using the same template but we want to use a different display template for the Titles property so we create a subclass of a Info model:
public class SpecificInfo : Info
{
[UIHint("SpecificTitles")]
[DataType("SpecificTitles")]
public override IEnumerable<string> Titles { get; set; }
}
But it's still trying to use the "Titles" display template presumably because the expression passed into the DisplayFor helper thinks that it's accessing the property on the Info class.
Is there any way to get that helper to use the correct display template? I've been thinking that a possible solution would be to create my own DisplayFor extension method that figures out what the runtime type of the model is and uses reflection to find the property and check to see if we are specifying a template there but I can't shake the feeling that there might be an easier way to do it.
You are right setting #model Info in your view makes DisplayeFor use htmlHelper<Info>. It has only acces to Info attributes. You can spcify in view which template to use:
#Html.DisplayFor(x=> x.Titles, Model is SpecificInfo ? "SpecificInfo" : "Info")
but then there is no reason for UIHintAttribute.
As you write you can also write custom DisplayFor method and you can use htmlHelper.ViewData.Model to get actual model attribute(as you suggested in comment ;)):
public static MvcHtmlString CustomDisplayFor<TModel, TValue>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> expression)
{
MemberExpression memberExpression = (MemberExpression)expression.Body;
var propertyName = memberExpression.Member is PropertyInfo ? memberExpression.Member.Name : (string)null;
var prop = htmlHelper.ViewData.Model.GetType().GetProperty(propertyName);
var customAttributeData = prop.GetCustomAttributesData().FirstOrDefault(a => a.AttributeType.Name == "UIHintAttribute");
if (customAttributeData != null)
{
var templateName = customAttributeData.ConstructorArguments.First().Value as string;
return DisplayExtensions.DisplayFor<TModel, TValue>(htmlHelper, expression, templateName);
}
return DisplayExtensions.DisplayFor<TModel, TValue>(htmlHelper, expression);
}
View:
#Html.CustomDisplayFor(x => x.Titles)
On MVC3, is there a way to decorate a ViewModel property in order to get the DefaultModelBinder to use a different name for it in the request?
For example, suppose you have the following view model:
public class SomeModel
{
public string Direction {get;set;}
}
But the parameter coming in is Dir from an external source (such as some third-party component, for example).
I know a custom model binder could handle that, but I assume there must be a way to decorate the property, similar to the way action parameters can use Bind(Prefix="...") in order to define that mapping.
You could always create another Property:
public class SomeModel
{
public string Direction {get;set;}
public string Dir
{
get { return this.Direction; }
set { this.Direction = value; }
}
}
I'd also mention that the ViewModel used in a view (cshtml/vbhtml) does not have to be the same ViewModel used on the Post Method.
OK, so after more research looking at similar questions and seeing the feedback here as well, it seems that the answer to my question is basically "NO".
There is no out-of-the-box way, so either custom binders must be used or or the properties should be renamed.
A similar question with a more detailed answer can be found here: How to bind URL parameters to model properties with different names
I was able to accomplish this in ASP.NET MVC Core using the FromForm attribute.
public class DataTableOrder
{
public int Column { get; set; }
[FromForm(Name = "Dir")]
public string Direction { get; set; }
}
Documentation: https://docs.asp.net/en/latest/mvc/models/model-binding.html#customize-model-binding-behavior-with-attributes
However, depending if you do a GET or a POST, you might want to use [FromQuery] instead of [FromForm] I suppose.
I am new to MVC3 and am still trying to pick up on the good programming practices. I had a heck of a time trying to format how a DateTime? was being displayed in my MVC3 project that doesn't have an explicit ModelName.cs file associated with the class the date was coming from.
We had a database already in place and use a .edmx (ours is called Pooling.edmx) that we get our models from. I obviously didn't want to edit the designer file to fit this widely accepted solution: Date only from TextBoxFor().
I then tried another solution which I found here: Using Html.TextBoxFor with class and custom property (MVC)
which uses:
#Html.TextBoxFor(m => m.Name, new { data_bind="value: Name", #class = "title width-7" })
This worked as I was able to use custom attributes, add class names, and set a Value all at once.
I transformed this:
#Html.TextBoxFor(m => Model.PrePoolOwner.OglDateEffective, new Dictionary<string, object> { { "class", "check-dirty input-small datePicker" }, { "data-original-value", #Model.PrePoolOwner.OglDateEffective } })
into this (which seems really ugly...and leads to me to the question):
#Html.TextBoxFor(m => Model.PrePoolOwner.OglDateEffective, new { data_original_value = Model.PrePoolOwner.OglDateEffective.HasValue ? Model.PrePoolOwner.OglDateEffective.Value.ToString("MM/dd/yyyy") : null, #class = "datePicker check-dirty", #Value = Model.PrePoolOwner.OglDateEffective.HasValue ? Model.PrePoolOwner.OglDateEffective.Value.ToString("MM/dd/yyyy") : null })
Is it better to find and use these other ways (like underscores will translate into dashes, etc) to display the information, or should I be having a ModelName.cs file to change how it is displayed at the Model level?
For some reason I feel having a huge Pooling.edmx file, that maps out our database, is limiting us now and will in the future on how we access/present/change data as the website evolves.
To get a "PrePoolOwner" object which is called up above by Model.PrePoolOwner.OglDateEffective, we have a PrePoolOwnerRow.cs file that does:
namespace OCC_Tracker.Models
{
public class PrePoolOwnerRow
{
public bool Dirty { get; set; }
public bool Delete { get; set; }
public PrePoolOwner PrePoolOwner { get; set; }
public PrePoolOwnerRow(PrePoolOwner owner)
{
this.Dirty = false;
this.Delete = false;
this.PrePoolOwner = owner;
}
public PrePoolOwnerRow()
{ }
}
}
We then call at the top of our .cshtml file
#model OCC_Tracker.Models.PrePoolOwnerRow
Ok, so a few suggestions.
First, in your example, PrePoolOwnerRow is your view model. This, in itself, is fine. But the code smell is where you expose PrePoolOwner -- a domain entity -- through your view model, PrePoolOwnerRow.
So first thing I would suggest is to update your view model to something more like this:
public class PrePoolOwnerModel
{
public bool Dirty { get; set; }
public bool Delete { get; set; }
public DateTime? OglDateEffective { get; set; }
public String OglDateEffective { get; set; }
// Other public properties here that map to properties on your PrePoolOwner entity.
}
All I did here was drop the reference to the domain model, and replace it with (a placehold comment to) the properties from your model, needed by your view.
In your controller, fetch your PrePoolOwner model, and map it to your view model using AutoMapper (this is a hypothetical example, as I don't know what your view is doing):
public ViewResult Index(int id)
{
PrePoolOwner entity = myservice.GetPrePoolOwner(id);
PrePoolOwnerModel model = Mapper.Map<PrePoolOwnerModel>(entity);
return View(model);
}
Now, to address the issue w/ the DateTime textbox, you should look at using MVC Editor Templates (this is another subject altogether, but Google it to find many topics covering the subject). This gives you more flexibility and re-usability over rendering elements of like types (i.e. DateTime).
But, aside from using that, you can add another property to your model, and use AutoMapper to set the DateTime appropriately. So, something like this in your controller, execpt you would set up a mapping in AutoMapper to handle this:
public class PrePoolOwnerModel
{
....
public String OglDateEffectiveValue { get; set; }
....
}
public ViewResult Index(int id)
{
....
model.OglDateEffectiveValue = model.OglDateEffective.HasValue ?
model.OglDateEffective.Value.ToString("MM/dd/yyyy") :
String.Empty;
....
}
Once that is set up, you can just use this new model property (OglDateEffectiveValue) for your attributes on your textbox.
I know there's a lot I covered there, but dig in and experiment with modeling your view models like this, and using AutoMapper to map the data to your view model exactly like you need it to be on your view.
Keep your view logic very simple. Avoid using anything crazy beyond the occasion loop, and maybe an if conditional here or there.
In an MVC application, I have some DropDownLists. In my controller I create the IEnumerable<SelectListItem>s and transfer them to my View. Unfortunately, if there is a validation error, I need to recreate those lists, otherwise the view rendering fails.
In the controller action method I'm doing:
var possibilities = _repo.Objects.Select(o=>new SelectListItem(...));
viewmodel.Possibilities = possibilities;
return View(viewmodel);
The view-model has the Possibilities property defined like this:
IEnumerable<SelectListItem> Possibilities { get; set; }
And in my view I access it:
#Html.DropDownListFor(vm=>vm.ThePossibility, vm.Possibilities)
The problem is that when the form post action method is called, the view model passed to it has a null for Possibilities, so when I call:
if(!ModelState.IsValid)
return View(model);
The view doesn't get rendered.
I understand why the propery is null on the post action method, but what's the best way of correcting this? I'd rather not reinitialize those lists.
Thanks.
If you don't want to re-initialize the lists, you will have to cache them somewhere, such as the session or somewhere else.
Frankly, in most cases, it's just simpler to rebuild them. You will have to re-assign them every time.
You should look into using the Post-Redirect-Get pattern; There is a nice pair of attributes described in this blog post that make this very easy to do in ASP.Net MVC.
I usually cache these somewhere or provide a static class for getting common lists. You can then provide access to these in your model with a getter.
For example:
IEnumerable<SelectListItem> _possibilities
IEnumerable<SelectListItem> Possibilities
{
get
{
if (_possibilities == null)
_possibilities = CommonLists.Possibilities();
return possibilities;
}
}
Accessors and JSON (NetwonSoft) are your friends.
In a nutshell
When you set the IEnumerable<SelectListItem> property of your model, serialize it to a public string property.
When your public string property is being set, and the IEnumerable<SelectListItem> is not defined (e.g. null), deserialize the string property to the IEnumerable<SelectListItem> property of your model.
In your view, embed the serialized string so that it is persisted between posts to the server. (Eg. #Html.HiddenFor(...))
Model
public class MyViewModel
{
public IEnumerable<SelectListItem> Selections
{
get
{
return selections;
}
set
{
selections= value;
// serialize SelectListItems to a json string
SerializedSelections = Newtonsoft.Json.JsonConvert.SerializeObject(value);
}
}
IEnumerable<SelectListItem> selections;
public string SerializedSelections
{
get
{
return serializedSelections;
}
set
{
serializedSelections = value;
if(Selections == null)
{
// SelectListItems aren't defined. Deserialize the string to the list
Selections = Newtonsoft.Json.JsonConvert.DeserializeObject<IEnumerable<SelectListItem>>(value);
}
}
}
string serializedSelections;
}
Razor View
<form>
...
#Html.HiddenFor(m => m.SerializedSelections)
...
#Html.DropDownListFor(m => m.SomeProperty, Model.Selections)
</form>