Ok, this is for me a very tough challenge. We're taking our existing ASP.NET website and converting (redesigning the PL only) to MVC. Our site is very complex. But the hard part is to convert the existing custom controls to MVC equivilant. The custom controls (I am not talking about user controls) are just of course a class currently that inherits System.Web.UI.Control and uses that object throughout. For example, we have some properties at the top of this existing custom class like so:
Dictionary<int, Control> configControls;
DropDownList kControl;
CheckBox confirmBox;
These all are variables of type Web controls in classic ASP.NET.
So I figured maybe what I could do (without building entire new custom controls from scratch) is to use the HtmlHelper object. So I tried this:
(include first the using statement that includes System.Web.MVC.Html at the top of my new custom class in our new web project)
private HtmlHelper helper;
Dictionary configControls;
helper.DropDownList
but this is not working. I guess I can't use this object just like this ?? I figured I can use HtmlHelper in the Dictionary and then make variable types off of helper. but those are just extension methods, not objects
I don't know of an equivalent to something like the generic "Control" we had available to us to inherit from such as in classic ASP.NET. Surely it won't be the same in MVC obviusly (stateless and a completely diff way of doing things) but what can I use in MVC with the same concept sort of?
So I figured maybe what I could do (without building entire new custom controls from scratch) is to use the HtmlHelper object. So I tried this:
(include first the using statement that includes System.Web.MVC.Html at the top of my new custom class in our new web project)
private HtmlHelper helper;
Dictionary configControls;
helper.DropDownList
but this is not working. I don't even know if this approach will work in my custom control. And when I try to use my helper variable, I get no extension methods unless it's inside an existing extension method where the signature has an HtmlHelper param passed in. So when I create that private variable just in my custom class outside, I get nothing in intellisense to choose from when doing "helper.". So do I need to define that object like this: ?
private HtmlHelper htmlHelper = new HtmlHelper();
but it's asking for a ViewContext and an IViewDataContainer as params. If I'm building out a custom method that knows nothing yet about its view (it shouldn't need to) because I'm simply creating strings of HMTL in this custom class to be passed to the Extension method to ultimately spit out fields then maybe I can't use HtmlHelper this way in a custom class like this.
So can I use that object in a way instead of "Control"? Maybe I can even in my dictionary variable use type object in place of control ? I don't know and then cast object to type HtmlHelper when I need to use or reference that value from the dictionary? But for now, I figured I can use HtmlHelper object in the Dictionary and then make variable types off of helper. but those are just extension methods, not objects.
I hope I am making any sense here when you read this.
I just blogged about this last night, some of this might be helpful for you.
WebForms And MVC In Harmony — Almost…
Basically it discusses some options for emulating "WebControls" using MVC.
Additionally, you can still use WebControls like you could before (granted they may not work if they need things like the ViewState). The problem I've discovered with that is you have a disconnect from the inline render code and the WebControls themselves.
I did write this method last night which let you use WebControls with inline code.
using System.Reflection;
using System.IO;
using System.Web.UI;
using System.Web;
using System.Web.Mvc;
public static class MyExtensionMethods {
//example method - renders a webcontrol to the page
public static void RenderControl(this HtmlHelper helper, Control control) {
//perform databinding if needed
MethodInfo bind = control.GetType().GetMethod("DataBind");
if (bind is System.Reflection.MethodInfo) {
bind.Invoke(control, null);
}
//render the HTML for this control
StringWriter writer = new StringWriter();
HtmlTextWriter html = new HtmlTextWriter(writer);
control.RenderControl(html);
//write the output
HttpContext.Current.Response.Write(writer.ToString());
//and cleanup the writers
html.Dispose();
writer.Dispose();
}
}
//then used like...
<% int[] numbers = { 1, 2, 3, 4, 5 }; %>
<% this.Html.RenderControl(new DataGrid() { DataSource = numbers }); %>
Just an interesting concept you might be interested in.
Short of hacking webforms controls into your MVC application, servercontrols with many methods do not map to MVC.
They are replaced by partials and controllers(or subcontrollers if you like that sort of thing).
If all you want to do is render some HTML based on a few parameters, then a Helper is what you are after. Static Class, static methods. If however, you need to keep state, and do a bunch of stateful stuff, then a partial, JS, and controller(or subcontroller) are really what you are after.
Server Controls that manage their own state really are a thing of the past in MVC.
Remember that MVC is an attempt to use the web the way it was meant to work, particularly if you bring REST into the picture. Webforms is a fudge to make the web work like windows forms.
I would create needed business logic, shared partial view (probably, with quite a lot of well structured javascript lines attached) and seperated controller.
Then i would use this bunch of code through partial request technique.
Not sure how much this will be of help but, do have a look at this series of blog post
Custom controls everywhere
Also have a look at the Catharsis project
Web-Application Framework - Catharsis - Part I - New Solution
The codeplex URL for the same is
Catharsis
This project has some good examples of control creating for asp.net mvc.
Related
I am building an application using Play for Model and Controller, but using backbone.js, and client side templating. Now, I want the html templates to be served by Play without any backing controller. I know I could put my templates in the public directory, but I would like to use Play's templating engine for putting in the strings in my template from the message file. I do not need any other data, and hence dont want the pain of creating a dummy controller for each template. Can I do this with Play?
You could create a single controller and pass in the template name as a parameter, but I am not sure if it is a good idea.
public static void controller(String templateName) {
// add whatever logic is needed here
renderTemplate("Controller/"+templateName+".html");
}
Then point all your routes to that controller method. Forget about reverse routing, though.
I think I would still rather have a separate controller method for each template. Remember that you can use the #Before annotation (see Play Framework documentation) to have the message string handling in exactly one place, that is executed before each controller method. By using the #With annotation you can even have this logic in a separate class.
You can use template engine from any place in your code:
String result = TemplateLoader.load("Folder/template.html").render(data);
I would like to create an extension helper with the following signature:
public static MvcHtmlString
BindMissingFor(this HtmlHelper
htmlHelper, Expression> expression )
I would like this method to reflect through the supplied expression model and look for bind-able properties that have not already been bound on the form.
The use case is I would like to have some Views that only allow the user to interact with a portion of my Model. But, I would like to persist the entire model between multiple views (a wizard).
My current solution is to use a hidden-input for each field I don't want displayed. I'll probably do the same thing with this extension method, but I would like it to do the work for me instead of copy/pasting.
Is there a way to inspect the current form for inputs/selects from within an HtmlHelper extension method?
There is no way an html helper to know what happens in other parts of your view such as inspecting other form fields unless you pass it as argument. Also it is not very clear what you mean by look for bind-able properties that have not already been bound on the form. For persisting state in a wizard you might take a look at the Html.Serialize helper currently situated in the MVC Futures assembly. The idea behind this helper is that it allows you to serialize some model object (marked as [Serializable]) as hidden field inside a form and get its value back in a controller action using the [Deserialize] attribute. Behind the scenes it uses WebForms ViewState. You can also encrypt it. It is a good way of persisting state on the client between multiple pages.
I'm going to write a ton of helpers for my app.
For many of them I'd like not to extend HtmlHelper, but to create another helper class instead, for two reasons: 1) to be able to write, say, Link.Home and Icon.Edit instead of Html.HomeLink and Html.IconEdit; 2) to be able to easily tell standard helpers from custom ones and where the latter are defined.
Is this possible? How?
Is this not recommended? why?
thanks
Well, "helper methods" are just extension methods. If you want to be able to write Link.Home in your views, you'll have to extend ViewPage and add property called Link of type, say, ILink. The ILink interface may itself be empty. Now all your views should inherit from MyViewPage and writing helper methods becomes a simple task:
public static string Home(this ILink link, string text)
{
// ...
}
This would be possible by subclassing HtmlHelper and calling the new class Link and Icon for example.
I would not recommend it for the simple reason that Html.HomeLink and Html.IconEdit clearly states what the method does, it outputs Html in the form of a HomeLink and and an Edit Icon.
Main question: Is there a better way to accomplish creating a reusable control?
So the idea was to make a paging control to basically stop from having to keep typing out practically the same markup on multiple views. It's taking this:
<%= Html.ActionLink("First", "Details", new RouteValueDictionary(new { parentForumId = Model.TopicId, pageNumber = Model.FirstPage, amountToShow = Model.AmountToShow }))%>
|
<%= Html.ActionLink("Previous", "Details", new RouteValueDictionary(new { parentForumId = Model.TopicId, pageNumber = Model.PreviousPage, amountToShow = Model.AmountToShow }))%>
|
<%= Html.ActionLink("Next", "Details", new RouteValueDictionary(new { parentForumId = Model.TopicId, pageNumber = Model.NextPage, amountToShow = Model.AmountToShow }))%>
|
<%= Html.ActionLink("Last", "Details", new RouteValueDictionary(new { parentForumId = Model.TopicId, pageNumber = Model.LastPage, amountToShow = Model.AmountToShow }))%>
And turning it into this:
<%= Html.Pager("View", "Controller", "RouteName", Model, new Dictionary<String, Object> { {"parentForumId", Model.ParentForumId}}, " ") %>
Where as you can see I pass in the needed view, controller, route name, model, and a dictionary used to add request variables onto the url for the link.
What I found is that I would have to make an extension method for the HtmlHelper class and essentially take what in ASP.Net was a full class (with nice methods like CreateChildControls) and jam it all into one main method that returns a string.
Is this the preferred way of doing this? One nice thing of the ASP.Net way was markup to class as in you have the html markup tag that would translate markup properties to class properties. It generally made for cleaner mark up but admittedly "fake" html. In this situation I have a method with what could be a mile long signature pumping out html. And since I don't have a base WebControl class, every control I make will have to have method calls with the same basic needs like say CssClass or ID.
Now with that being said, I suppose I could pass in an attributes dictionary since the
HtmlHelper.GenerateRouteLink
method that I'm using calls for one anyhow, but this really seems really messy.
Is there a better way to do this?
First, its all ASP.NET...one is MVC, the other is WebForms. Took me a sec to realize what you were saying when you keept saying the "ASP.NET way". :P
The idea with an MVC is that your view is "dumb", without any real behavior outside of the absolute bare bones basics to render data. In WebForms, views were tightly bound to the behavior that rendered them and handled view events. This, while convenient, made WebForms views very difficult to unit test since view content and behavior were linked and sometimes blended.
The reason MVC views use things like HtmlHelper and AjaxHelper is to keep behavior as separated from the view as possible. Unlike a user or server control in WebForms, you can fully unit test an Html.Pager extension method, since the logic is pure code, without blending those UI concerns or being linked to a bunch of non-testable UI level types. The same general rule applies to MVC controllers to...they are just code, without being linked to events or anything like that.
It may be less convenient in the short run, as you are currently used to the old WebForms way of doing things. Give yourself some time, though, and you will likely start to realize the benefits that MVC's preferred way of doing things brings to the table. Writing a Pager extension method on HtmlHelper is indeed the preferred way to do things with MVC.
As for the mile-long signature bit...do a search (try out Bing.com!) for fluent style interfaces and HtmlHelper. The fluent style is starting to take a strong hold in environments like MVC views where you are likely to have huge signatures. The general idea is based on method chaining, kind of like jQuery, and can shorten those long signatures into a series of much shorter and more meaningful chained method calls that set up your html helper, with a final call to a .Render method or something similar.
You could put it in a partial view, instead of creating a helper.
You might want to check out Martijn Boland's Pager control for some inspiration.
Personally, for my reusable grid control I use a class that contains all information needed to generate a grid with paging, sorting, ... and I call partial views to generate the seperate elements (Pager, Column selection, pagesize selection, ...), passing the information they require to them.
This way I can easily extend the grid with custom stuff. For example I can create a Mygrid_editableTable.ascx view to show textboxes instead of just text, and add an extra column with a submit button. This while continuing to use the paging, page selection, ...
We end up using html helpers for paginators as they are easy to unit test. Pagination business requirements can be finicky.
"Show less than 35 links as numbers, then group by 20s unless there are more than 100 pages of results, in which case group by 100s...but on Thursdays, or to GoogleBot, show them as... etc."
Plus our SEO guys keep changing their mind on what shape urls get the most juice. In such a situation, something unit testable is a must!
I make heavy use of View Components in some of the larger applications I've built in Monorail - What is the equivalent approach in ASP.Net MVC for a view component, that can support sections etc.?
Actually you have several options to create the equivalent of a ViewComponent in ASP.NET MVC, depending in the complexity of your component. I use these two approaches which are the more mvc-ish of the options I am aware of.
1:
The simplest thing is to create a ViewUserControl and display it using Html.RenderPartial with the helper. The ViewUserControl is a simple piece of markup with no backing controller (I think you can put a codebehind file if you want).
Optionally, you can pass a model object or the entire ViewData dictionary to the view when calling RenderPartial, like this:
<% Html.RenderPartial("TopBar", model); %>
"TopBar" is an ascx page. This works anywhere, in master pages and in normal views.
2:
If you want your component to have more complicated logic or to access datasources, IoC, etc, then you can use Html.RenderAction which is an extension method found in the Microsoft.Web.Mvc assembly. I am using this out of the mvccontrib distribution. It works like this, you need to create a normal controller with all the logic you need, then create some views and all of these things become your component, for example:
public class AboutComponentController : Controller {
public IRepository Repository{ get; set; }
public ActionResult Detail() {
var lastEvent = Repository.FindAll<Auditoria>().FirstOrDefault();
return View(lastEvent);
}
}
Notice how I have a reference to an IRepository which is going to be injected with IoC (Windsor in my case) and I can do anything a normal controller would do.
Now, in any page (master or normal) where you want to use your component, import Microsoft.Web.Mvc and call Html.RenderAction with the appropriate parameters. This will create a mini mvc pipeline that creates the controller, resolves the view, etc., just like a Monorail ViewComponent. I prefer to use the lambda based variation of the method, like this:
<% Html.RenderAction<AboutComponentController>(x => x.Detail("a message"));%>
Unfortunately, the only way to pass parameters is to use the method call itself, which in turn must be unique in the controller. Still needs some work to resemble a ViewComponent.
I don't use masterpages or layouts in the views of my components since they are composition elements themselves.
Remember that when using the Webforms view engine, you can have strongly typed views if you like to have intellisense when using the Model variable in code blocks.
The beauty of this is that you can mix view engines with these approaches, I usually create the components in nvelocity and display them in aspx pages, etc.
I now there can be issues with caching of the partial views but I haven't run into any so far. I am sure there are other options (like subcontrollers in mvccontrib) but this is usually enough for simple cases. Of course you can use normal ASP.net components in your aspx view pages but that would be cheating right? hehe. I hope it helps.
Phil Haack blogged about creating areas to group controllers into sub-folders/sections similar to MonoRails.