i have the following situation.
I am developing a MVC 3 asp.net application. I want to make a searh form with datetime range filter. I wan to use a datepicker so the user can select the date range.
I have followed this mvc tutorial
http://www.asp.net/mvc/tutorials/javascript/using-the-html5-and-jquery-ui-datepicker-popup-calendar-with-aspnet-mvc/using-the-html5-and-jquery-ui-datepicker-popup-calendar-with-aspnet-mvc-part-4
My problem is that i can not get to link the editor template to a NON MODEL #editotfor or input or #textbox, i`ll put some code so you get my point.
I have the following EditorTemplate in my Shared folder (file Date.cshtml)
#model DateTime
Using Date Template
#Html.TextBox("", String.Format("{0:d}", Model.ToShortDateString()),
new { #class = "datefield", type = "date" })
if I use this line in my view everything works
#Html.EditorFor(m => m.fecha_Modificacion,"Date")
But i want to use the datepicker in a NON model value just a simpletextbox textbox
I have been trying this :
#Html.TextBox("fechaInicio", "", new { #class = "datefield" })
But not working
I´d apreciate any help
UPDATE
I must explain my problem in more detail
I have a
class Foo{
private stirng name;
private DateTime dateOfBirthday;
}
In the Foo view i am editing the list.cshtml so i can searh by dateOfBirthday by from to.
So i want to make two textBoxes so the user can select with a date time picker the from anf to date range.
I´d like to use a template so i can re-use it.
But the list.cshtml view is already typed to #Model Foo
but the two textboxes are not model fields
The EditTemplate i have made work perfectly on model typed #EditorFor filds so i am trying to use on my from to date range textboxes
For what i have read i can create a EditorTemplate but that does not mean that i use everywhere i can use just where i want
Thank you very much
From design perspective don't do non model things. Create view model that will contain everything the view needs to display and post back to controller, so you have everything you need in one class. This way strongly typed class is so much easier to work with.
With regards to your concrete question, in order for you to use Editor Template, all you need to do is to create folder EditorTemplates in the following hierarchy: Views/<ControllerName>/EditorTemplates
and put the <TypeYouWantToUseThisEditorTemplate>.cshtml inside editor templates folder and have #model <<TypeYouWantToUseThisEditorTemplate> as one of the first lines in that editor template.
The link to editor template is the type of the property. For example if in the model you have property of type MyType, editor template needs to be called MyType.cshtml.
That is all you have to do.
This will also work with primitive types, so if you create file string.cshtml under editor templates folder any string that your view isusing will go to that editor template.
Note that if your editor template will be used across multiple views and controllers, you can create your editor templates under /Views/Shared/EditorTemplates so its shared across application.
More about editor templates here: ASP.NET MVC 3 – How to use EditorTemplates
Hope this helps.
UPDATE
following your request, here's simple example of editor template used with view model (give you the idea how the whole construction works)
View Model:
public class Book
{
public int BookId { get; set; }
public string BookName { get; set; }
public string Description { get; set; }
}
EditorTemplate called Book.cshtml and located under /Views/<ControllerName or Shared/EditorTemplates:
#model <MyAppName>.Models.Book
#Html.DisplayFor(p => p.BookId)
#Html.EditorFor(p => p.BookId)
#Html.DisplayFor(p => p.BookName)
#Html.EditorFor(p => p.BookName)
#Html.DisplayFor(p => p.Description)
#Html.EditorFor(p => p.Description)
No error handling, no validation, no nothing is done inside this editor template for sake of brevity.
View page that is using editor templates with type Book will have to do no work at all (all the work will be done by editor template):
#Html.EditorFor(model => model.Book)
You can use a ViewModel to help you
Sometimes they can save your day and its considered a best practice to use them when you are using models from EF (Entity Framework) or other persistance aware classes that have important properties that can't be changed by the user on post for example.
Something like this may help you:
public class FooSearchViewModel
{
public Foo Foo { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set }
}
Then, you have to bind the view to this ViewModel instead of the model and use it like:
Name: #Model.Foo.name
From: #Html.EditorFor(model => model.From, "DateWithCalendar")
To: #Html.EditorFor(model => model.To, "DateWithCalendar")
The template can be archieved with a EditorTemplate, like the one #Display Name explained in his answer:
/Views/Shared/EditorTemplates/DateWithCalendar.cshtml
#model System.DateTime
#Html.TextBoxFor(model => model.Date, new { class = "date datepicker" })
Then you can use CSS to customize the input and apply the jQuery UI datepicker to it or wethever you like.
You can also define the template to use with DataAnnotations attributes in the ViewModel:
[UIHint("DateWithCalendar")]
public DateTime From { get; set; }
References:
http://blogs.msdn.com/b/simonince/archive/2010/01/26/view-models-in-asp-net-mvc.aspx
http://kazimanzurrashid.com/posts/asp-dot-net-mvc-viewmodel-usage-and-pick-your-best-pattern
ViewModel Best Practices
Try using partial views:
#* Views\Shared\_HandlingMyString.cshtml *#
#model string
This is my partial view to handle the string: '#Model'
Than you will call it this way from your main view (and I think this is the link you're looking for):
#model MyType // that has public string MyProperty that holds your date (start and end)
#Html.Partial("_HandlingMyString", Model.MyProperty)
Something along these lines. You might need to do some twicking, I didn't compile this code, but its is what you seems needing.
Please let me know if this helps.
De solution proposed by rcdmk did the work.
I think that polutae a class for search porposes is the right way to do it.
I think that working with ASP.net MVC we must use string typed views as Display Name pointed.
Thank you for your help.
Related
I have a simple model:
public class MyViewModel {
public MyCustomType EmailAddress {get; set; }
}
In a certain view, I need to present an input box to allow editing of this property. Ideally I want to keep using the standard #Html.EditorFor syntax, so I've created a /Views/Shared/EditorTemplates/MyCustomType.cshtml template:
#model MyNamespace.MyCustomType
#Html.TextBoxFor(m => m.SomeStringProperty, new { #class = "form-control" })
I also have a custom IModelBinder to read this input back into MyCustomType when it's submitted, this all works fine.
However, as you can see from the property name, this property contains an Email address. I'd like to use the standard data annotations attributes to mark it as such, and then update MyCustomType.cshtml to make use of it to render an <input type="email"> input box when necessary - not all uses of MyCustomType are email addresses, hence wanting to use the standard attributes to mark it as such. When I do so:
public class MyViewModel {
[EmailAddress]
public MyCustomType EmailAddress {get; set; }
}
My custom template is no longer used - it instead reverts back to using the standard EditorFor code, and essentially just displays the .ToString() version of MyCustomType inside an <input>, rather than my custom editor template which pulls out a specific property.
Is there a way to make it use my custom editor template functionality, while still only requiring models to add the standard data annotations attributes?
Data annotations always take precedence over the type when it comes to editor templates. You have a few options:
Create a custom "EmailAddress" attribute. Technically, I think you could pretty much just subclass EmailAddressAttribute without actually adding anything additional. The name of your custom attribute, though, would allow you to have a different editor template for that.
Go ahead and use the EmailAddress.cshtml editor template, but branch inside. Assuming MyCustomType inherits from String:
#model String
#if (Model is MyCustomType)
{
...
}
else
{
...
}
Probably the easiest method, though, is to simply specify the template. It's not quite as automatic, but it doesn't require any additional work:
#Html.EditorFor(m => m.EmailAddress, "MyCustomType")
Which would then load MyCustomType.cshtml instead of the default EmailAddress.cshtml.
This is mostly a follow-up to a comment in this issu, but I don't have enough reputation to comment ...
ASP.Net MVC Postback a label value to your controller
Let's say I have a simple model:
public class SimpleClass
{
public String Label { get; set; }
public String FirstName { get; set; }
}
Label is changed based on user/client so it can't be a DataAttribute. If when posted back processing problems occur, we need to redraw the entire page. This is the crux of the problem of the previous post. The accepted solution is to do this:
#Html.DisplayTextFor(model => model.Label)
#Html.HiddenFor(model => model.Label)
#Html.EditorFor(model => model.FirstName)
That makes sense in that it works. But our models are much more complicated and extensive. This method will result in a ton of hidden fields which seems like a very dirty solution.
This brings me to JP's comment:
ASP.Net MVC Postback a label value to your controller
The solution there is to reload the model. But it's not just a reload, it's also a merge since you want to preserve any client-side data changes.
default: SimpleClass { Label="TheLabel", FirstName="Rob"}
postedback: SimpleClass { Label="", FirstName="Steve" }
we want: SimpleClass { Label="TheLabel", "FirstName="Steve" }
My question is does MVC have a good way to know what fields were postedback so it merges correctly? We would need to only merge postedback fields not blank properties.
Or is it better to just ajaxify the entire postback and not do a form submit? This avoids all model reload issues on submit.
Update
To give Pablo credit I accepted his solution. To see my simple example of his solution, check Robert Harvey's comment in the Answers below:
ASP.Net MVC Postback and Models
The main problem here is in trying to fit WebForms' PostBack concepts into MVC. There is no such thing as a stateful postback where things just automatically retain their state.
You only have ViewModels that are bound to the view, and ViewModels that are posted by the view to the Controller. They don't even necessarily need to be of the same Type. Meaning, the controller should only receive the data that the user indeed can change, not large objects with many properties that were part of the initial ViewModel but are read-only.
Labels commonly represent read-only texts and they are not editable form elements. Which is why you have to use hidden fields for that.
And yes, sometimes that implies that you have to reload the original data in the controller, and sync up with new data that you posted, which isn't necessarily a bad thing. If you bind read-only data to a view, which the user can't manually edit, you shouldn't really trust that data coming back in a post afterwards. Just because your html might try to make it read-only doesn't mean I can't manipulate the post and ultimately change your "read-only" data without you knowing.
I just read the second question you mentioned, and from the looks of it, his main problem was that he was trying to reuse the same ViewModel again, so all the data was missing and the model wasn't valid. The solution to that is indeed quite simple, ONLY post what you need, as a new ViewModel type, and have the controller take care of the rest.
[Moved from OP]
I think this is what Pablo is suggesting for those who are wondering. It seems to be a good pattern to resolve this problem.
Models:
public class SimpleClass : SimpleClassPostBack
{
public String Label { get; set; }
public SimpleClass()
{
// simulate default loading
Label = "My Label";
FirstName = "Rob";
}
}
// contains only editable by the user fields
public class SimpleClassPostBack
{
public String FirstName { get; set; }
}
Controller Actions:
[HttpGet]
public ActionResult SimpleClassExample3()
{
SimpleClass simpleClass = new SimpleClass();
return View(simpleClass);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SimpleClassExample3(SimpleClassPostBack postBackSimpleClass)
{
Boolean errorOccurred = true;
if (!errorOccurred)
{
// do whatever success action is necessary
}
// redraw the page, an error occurred
// reload the original model
SimpleClass simpleClass = new SimpleClass();
// move the posted back data into the model
// can use fancy reflection to automate this
simpleClass.FirstName = postBackSimpleClass.FirstName;
// bind the view
return View(simpleClass);
}
View:
#model SimpleClass
#{
ViewBag.Title = "Simple Class Example3";
}
<h2>Simple Class Example3</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<label for="FirstName">#Html.DisplayFor(m => m.Label)</label>
#Html.EditorFor(m => m.FirstName)
<br/>
<button>Submit</button>
}
You should only send data from the client to the server that the server can't "figure out" on its own. If the server knows what the labels were when the user first navigated to that view then if the user cannot modify them, the server will be able to know what the labels are when reloading the view.
Use hidden fields to identify the database objects. So your SimpleClass should probably have some sort of Id which you will use in the hidden input. Use the EditorFor for FirstName. Now when the form is posted, use the sent Id to find the correct SimpleClass from the database and modify its FirstName property with the value posted. The Label property will be null which is ok since you don't need to save it. Now if there's a problem in the post and you want to send the same view back like it was, you need to repopulate the Label the same way you did when the user arrived to the view for the first time. The values of Id and FirstName properties will be automatically sent back to the view with the model state.
In summary:
Only post data that is needed to identify something and what the user
can edit in that view.
Don't trust the client to send you anything valid. The user can change the values of the hidden field labels to anything.
This question is a follow-up for Why is my DisplayFor not looping through my IEnumerable<DateTime>?
A quick refresh.
When:
the model has a property of type IEnumerable<T>
you pass this property to Html.EditorFor() using the overload that only accepts the lambda expression
you have an editor template for the type T under Views/Shared/EditorTemplates
then the MVC engine will automatically invoke the editor template for each item in the enumerable sequence, producing a list of the results.
E.g., when there is a model class Order with property Lines:
public class Order
{
public IEnumerable<OrderLine> Lines { get; set; }
}
public class OrderLine
{
public string Prop1 { get; set; }
public int Prop2 { get; set; }
}
And there is a view Views/Shared/EditorTemplates/OrderLine.cshtml:
#model TestEditorFor.Models.OrderLine
#Html.EditorFor(m => m.Prop1)
#Html.EditorFor(m => m.Prop2)
Then, when you invoke #Html.EditorFor(m => m.Lines) from the top-level view, you will get a page with text boxes for each order line, not just one.
However, as you can see in the linked question, this only works when you use that particular overload of EditorFor. If you provide a template name (in order to use a template that is not named after the OrderLine class), then the automatic sequence handling will not happen, and a runtime error will happen instead.
At which point you will have to declare your custom template's model as IEnumebrable<OrderLine> and manually iterate over its items in some way or another to output all of them, e.g.
#foreach (var line in Model.Lines) {
#Html.EditorFor(m => line)
}
And that is where problems begin.
The HTML controls generated in this way all have same ids and names. When you later POST them, the model binder will not be able to construct an array of OrderLines, and the model object you get in the HttpPost method in the controller will be null.
This makes sense if you look at the lambda expression - it does not really link the object being constructed to a place in the model from which it comes.
I have tried various ways of iterating over the items, and it would seem the only way is to redeclare the template's model as IList<T> and enumerate it with for:
#model IList<OrderLine>
#for (int i = 0; i < Model.Count(); i++)
{
#Html.EditorFor(m => m[i].Prop1)
#Html.EditorFor(m => m[i].Prop2)
}
Then in the top-level view:
#model TestEditorFor.Models.Order
#using (Html.BeginForm()) {
#Html.EditorFor(m => m.Lines, "CustomTemplateName")
}
which gives properly named HTML controls that are properly recognized by the model binder on a submit.
While this works, it feels very wrong.
What is the correct, idiomatic way to use a custom editor template with EditorFor, while preserving all the logical links that allow the engine to generate HTML suitable for the model binder?
After discussion with Erik Funkenbusch, which led to looking into the MVC source code, it would appear there are two nicer (correct and idiomatic?) ways to do it.
Both involve providing correct html name prefix to the helper, and generate HTML identical to the output of the default EditorFor.
I'll just leave it here for now, will do more testing to make sure it works in deeply nested scenarios.
For the following examples, suppose you already have two templates for OrderLine class: OrderLine.cshtml and DifferentOrderLine.cshtml.
Method 1 - Using an intermediate template for IEnumerable<T>
Create a helper template, saving it under any name (e.g. "ManyDifferentOrderLines.cshtml"):
#model IEnumerable<OrderLine>
#{
int i = 0;
foreach (var line in Model)
{
#Html.EditorFor(m => line, "DifferentOrderLine", "[" + i++ + "]")
}
}
Then call it from the main Order template:
#model Order
#Html.EditorFor(m => m.Lines, "ManyDifferentOrderLines")
Method 2 - Without an intermediate template for IEnumerable<T>
In the main Order template:
#model Order
#{
int i = 0;
foreach (var line in Model.Lines)
{
#Html.EditorFor(m => line, "DifferentOrderLine", "Lines[" + i++ + "]")
}
}
There seems to be no easier way of achieving this than described in the answer by #GSerg. Strange that the MVC Team has not come up with a less messy way of doing it.
I've made this Extension Method to encapsulate it at least to some extent:
public static MvcHtmlString EditorForEnumerable<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable<TValue>>> expression, string templateName)
{
var fieldName = html.NameFor(expression).ToString();
var items = expression.Compile()(html.ViewData.Model);
return new MvcHtmlString(string.Concat(items.Select((item, i) => html.EditorFor(m => item, templateName, fieldName + '[' + i + ']'))));
}
There are a number of ways to address this problem. There is no way to get default IEnumerable support in editor templates when specifying a template name in the EditorFor. First, i'd suggest that if you have multiple templates for the same type in the same controller, your controller probably has too many responsibilities and you should consider refactoring it.
Having said that, the easiest solution is a custom DataType. MVC uses DataTypes in addition to UIHints and typenames. See:
Custom EditorTemplate not being used in MVC4 for DataType.Date
So, you need only say:
[DataType("MyCustomType")]
public IEnumerable<MyOtherType> {get;set;}
Then you can use MyCustomType.cshtml in your editor templates. Unlike UIHint, this does not suffer from the lack of IEnuerable support. If your usage supports a default type (say, Phone or Email, then prefer to use the existing type enumeration instead). Alternatively, you could derive your own DataType attribute and use DataType.Custom as the base.
You can also simply wrap your type in another type to create a different template. For example:
public class MyType {...}
public class MyType2 : MyType {}
Then you can create a MyType.cshtml and MyType2.cshtml quite easily, and you can always treat a MyType2 as a MyType for most purposes.
If this is too "hackish" for you, you can always build your template to render differently based on parameters passed via the "additionalViewData" parameter of the editor template.
Another option would be to use the version where you pass the template name to do "setup" of the type, such as create table tags, or other kinds of formatting, then use the more generic type version to render just the line items in a more generic form from inside the named template.
This allows you to have a CreateMyType template and an EditMyType template which are different except for the individual line items (which you can combine with the previous suggestion).
One other option is, if you're not using DisplayTemplates for this type, you can use DisplayTempates for your alternate template (when creating a custom template, this is just a convention.. when using the built-in template then it will just create display versions). Granted, this is counter-intuitive but it does solve the problem if you only have two templates for the same type you need to use, with no corresponding Display template.
Of course, you could always just convert the IEnumerable to an array in the template, which does not require redeclaring the model type.
#model IEnumerable<MyType>
#{ var model = Model.ToArray();}
#for(int i = 0; i < model.Length; i++)
{
<p>#Html.TextBoxFor(x => model[i].MyProperty)</p>
}
I could probably think of a dozen other ways to solve this problem, but in all reality, any time I've ever had it, I've found that if I think about it, I can simply redesign my model or views in such a way as to no longer require it to be solved.
In other words, I consider having this problem to be a "code smell" and a sign that i'm probably doing something wrong, and rethinking the process usually yields a better design that doesn't have the problem.
So to answer your question. The correct, idiomatic way would be to redesign your controllers and views so that this problem does not exist. barring that, choose the least offensive "hack" to achieve what you want.
Use FilterUIHint instead of the regular UIHint, on the IEnumerable<T> property.
public class Order
{
[FilterUIHint("OrderLine")]
public IEnumerable<OrderLine> Lines { get; set; }
}
No need for anything else.
#Html.EditorFor(m => m.Lines)
This now displays an "OrderLine" EditorTemplate for each OrderLine in Lines.
You can use an UIHint attribute to direct MVC which view you would like to load for the editor. So your Order object would look like this using the UIHint
public class Order
{
[UIHint("Non-Standard-Named-View")]
public IEnumerable<OrderLine> Lines { get; set; }
}
I have a model class that goes like
public class Mod
{
public string StaticProp1 {get; set;}
public string StaticProp2 {get; set;}
public string EditableProp1 {get; set;}
}
I want a view in which I can edit EditableProp1 but where StaticProp1, StaticProp2 are displayed but not editable.
I created a strongly-typed view :
#model Mod
#using (Html.BeginForm())
{
<p>#Model.StaticProp1</p>
<p>#Model.StaticProp2</p>
#Html.TextBoxFor(m => m.EditableProp1)
<input type="submit" value="Save" />
}
In my Controller, when I deal with the action I find the EditableProp1 fine.
[HttpPost]
public ActionResult Edit(Mod model, FormCollection collection)
{
string editableProp = model.EditableProp1; //This works fine
string staticProp1 = model.StaticProp1; //Missing
return View(model);
}
This causes a problem if I post back for some reason as staticProp1 will now be null and the view won't be able to display it.
I know that I can add
#Html.HiddenFor(m => m.StaticProp1)
#Html.HiddenFor(m => m.StaticProp2)
to my view and that it will work fine, but I am wondering if there is another better way.
My values are already on the form (<p>#Model.StaticProp1</p>). Is there a way to bind the model to un-editable tags like that? Is there an HTML helper that does something like this?
By the way, if it isn't obvious, I am just starting out with MVC so if I am completely missing the point please let me know!
Every property of a model you want to persist has to be in the form (in an editor or hidden field). You can use, as you propose, Html.HiddenFor() for this. If you want to avoid overloading your view with hidden fields, you could store only the id of an entity in the hidden field and fetch the rest of the data based on the id in the Post action. Or use Html.TextBoxFor() with a readonly attribute, see this question for more information about it (I like the approach in the second answer as well).
I think the question relates more to model binding and how it works. If you don't want to use hidden field here (which I think fits your scenario), you can custom Model Bind by inheriting a class from:
DefaultModelBinder
I first designed my database, then used the EF designer to create a model for use in my MVC app. Most of the nvarchar colums in my database have empty strings as defaults, and they do not allow nulls.
How do I configure the fields to allow empty strings in my MVC 3 app?
<div class="editor-label">
#Html.LabelFor(model => model.Phone2)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Phone2)
#Html.ValidationMessageFor(model => model.Phone2)
</div>
The property “Phone2” can be empty, but the default behavior of EF/MVC seems to be to make the field required.
I’ve created a partial class to extend the classes generated by EF, and I also have a MetadataType class to add additional DataAnnotation attributes to the model.
[MetadataType(typeof(MyClassMetadata))]
public partial class MyClass
{
// Adding more properties and methods here, mostly to support use of enums in EF.
}
public class MyClassMetadata
{
// Empty strings are ok here!
public string Phone2 { get; set; }
[AllowHtml]
[UIHint("Html")]
public string Text { get; set; }
}
How do I make a field “not required”? I know I could just skip the validation in my view (use plain controls), but it just seems more “correct” to add the desired behavior to my model instead.
Empty string is allowed by default so you don't need to add any attribute. Moreover:
I could just skip the validation in my
view (use plain controls), but it just
seems more “correct” to add the
desired behavior to my model instead.
That is actually incorrect assumption. It is used in the simplest applications but generally it is considered as a bad practice. ViewModel is responsible for defining validation of user inputs. You can have different validation for edit and insert - how would you achieve that by data annotations on single class?