asp.net mvc and duplication - asp.net-mvc

I am finding the asp.net mvc framework is leading me towards a fair bit of duplication. Maybe I just don't know of the correct way to do things, so I will list some of the areas and maybe someone can suggest the correct approach:
Controller methods that do not accept generic parameters. I have multiple classes, such as:
class A
class B : A
class C : A
class D : A
and controller methods:
EditB(B obj)
EditC(C obj)
EditD(D obj)
All controllers do exactly the same thing, but I am assuming that 1. I can't have a generic controller parameter, and 2. the model binder will only bind to class A if I do:
EditA(A obj)
The next problem I have is with partials. To get model binding to work easily, I use methods such as:
Html.TextboxFor(m => m.blah.blahID)
but when a partial is shared between multiple views that only have m.blah in common, I do:
RenderPartial("BlahPartial", m.blah);
The problem is that now I would assume that using
Html.TextboxFor(m => m.blahID)
inside the partial will not give me what I want as it is missing the blah.
Finally, in controller methods, I find it difficult to extract common algorithms into invidiual methods as:
if (a)
return ActionResult("Blah");
else
return ActionResult("Home");
(or RedirectToAction or return View("Home") etc.)
seem diffuclt to put in private methods as I cannot return those things in anything other then the original controller.

I'm not sure exactly if I can put you on the right track as your question is pretty generic, but I think that you'll likely have to have separate controller actions per model. That doesn't mean that you can't share code. One way to do that would be to have a common base controller from which each specific controller derives. The code common to all controllers/models can be refactored back to the base controller.
As far as partials go, you can provide the prefix via ViewData -- see http://davybrion.com/blog/2011/01/prefixing-input-elements-of-partial-views-with-asp-net-mvc/
I'm not sure why you think that you can't easily have private methods. It's not difficult to have a private method that returns a particular model or uses a particular view by name.
private ActionResult RedirectOnSuccess<T>( T model, string action ) where T : IBaseModel
{
if (model.IsValid())
{
// save to database, etc.
return RedirectToAction( action );
}
return View( model ); // will re-render the view corresponding to the called action
}
Then call it as
public ActionResult Edit( SpecificModel model )
{
return RedirectOnSuccess( model, "index" );
}

Related

Orchard data entry forms without admin

We're working diligently at learning Orchard, with a goal of creating not websites, but business-centric web applications that we would normally write in months using MVC, but hope to be much more efficient by using the various Parts already available.
The last mile, though, seems to be a big block -- how to tell Orchard that it should create a shape that allows the end-user to edit some data? There's a good bit on most of end-user editing at Creating a module for Orchard that stores data from the front-end but it picks up after the data's already been entered and carries it through the controller's POST operation. What I can't figure out is how to get through the initial GET operation.
To elaborate, in straight MVC I might allow the user to enter information about themselves. So I'd have /Controllers/PersonController.cs and there write a Create() function. I'd add a View in /Views/Person/Create.cshtml and simply "return View()" from the controller. In Create(Person p), an HTTPGet method, I'd do the heavy lifting to save the object.
Now in Orchard I have my PersonPart and its PersonPartDriver which, as I understand from the above article, I would write my POST method to accept the PersonPart and save the object.
class PersonController : Controller
{
private readonly IRepository<PersonPartRecord> personRepository;
public PersonController(IRepository<PersonPartRecord> _personRepository) {
personRepository = _personRepository;
}
[HttpPost]
public Create(PersonPart part) {
personRepository.Create(part.Record);
}
}
All well, but how would I get Orchard to invoke the GET Editor(PersonPart, dynamic) method to get the form up for the user to do the initial data entry?
protected override DriverResult Editor(PersonPart part, dynamic shapeHelper)
{
return ContentShape("Parts_Person_Edit",
() => shapeHelper.EditorTemplate(
TemplateName: "Parts/Person",
Model: part,
Prefix: Prefix));
}
Or do I write the GET Create() method in the controller? If I do that, though, I'm bypassing the entire shape creation system, no? Something itches in the back of my brain saying I should rather be doing a Display() and, in the template, just making it an editable form, but I have a Display() for the readonly view of Person ... how to make it know I want the editable view?
Hope the question makes sense, and hope that someone can assist.
Thanks.
Have a look to Orchard.CustomForms
var model = _contentManager.BuildEditor(contentItem);
return View(model);
but you'll need something like the code above. You could return ShapeResult(this,model) too

Can you remove the HTML Field Prefix from strongly typed models in MVC 3?

I have a view model like this:
public class EditVM
{
public Media.Domain.Entities.Movie Movie { get; set; }
public IEnumerable<Genre> Genres { get; set; }
}
Movie is the real entity I wish to edit. Genres is simply present to populate a drop down. I would prefer that when I call:
#Html.TextBoxFor(m => m.Movie.Title)
inside my strongly typed view that the input control have a name = "Title" instead of "Movie.Title"
I do not wish to split my view into partial views or lose my strongly typed view by using ViewData or the like.
Is there a way to express to the View that I do not wish to have the Movie. prefix? I noticed that you can set:
ViewData.TemplateInfo.HtmlFieldPrefix = "x";
in the controller, but unfortunately it seems only to allow adding an additional prefix. Setting it to "" does nothing.
Is there any work around for this? Or am I stuck with the unfortunate prefix that isn't really necessary in this case if I wish to keep strongly typed views and lambdas?
Thanks for any help.
Update:
Here's the controller actions to maybe make things a bit clearer.
public ActionResult Edit(int? id)
{
var vm = new EditVM
{
Movie = id.HasValue ? _movieSvc.Find(id.Value) : new Movie(),
Genres = AppData.ListGenres()
};
return View(vm);
}
[HttpPost]
public void Edit([Bind(Prefix = "Movie")]Movie m)
{
_movieSvc.AddOrUpdateMovie(m); //Exceptions handled elsewhere
}
No, in order to do what you want you would have to rewrite the Html helpers, and then you would have to write your own model binder. Seems like a lot of work for minimal gain.
The only choice is a Partial view in which you pass the Movie object as the model. However, this would require you to write your own model binder to have it be recognized.
The reason you have to do m.Movie.Title is so that the ID has the correct name, so the model binder can recognize it as a member of your model.
Based on your update:
Your options are:
Use non-strongly typed helpers.
Use a partial view.
Rewrite the stronly typed helpers
Don't use the helpers at all, and write the values to the HTML
Personally, i'd just use 1 or 2, probably 2.
EDIT:
Based on your update above. Change your code to this (note, Genres does not get posted back to the server, so m.Genres will just be null on postback):
[HttpPost]
public void Edit(EditVM m)
{
_movieSvc.AddOrUpdateMovie(m.Movie); //Exceptions handled elsewhere
}
EDIT:
I did just think of an alternative to this. You could simply do this:
#{ var Movie = Model.Movie; }
#Html.TextBoxFor(m => Movie.Title)
However, if there was a validation error, you would have to recreate your EditVM.
I have a view model like this
I think that you might have some misunderstanding about what a view model is. A view model shouldn't contain any reference to your domain models which is what those Movie and Genre classes seem to be. I mean creating a new class that you suffix with VM and in which you stuff all your domain models as properties is not really a view model. A view model is a class that is specifically designed to meet the requirements of your view.
A much more correct view model would looks like this:
public class EditVM
{
public string MovieTitle { get; set; }
public IEnumerable<GenreViewModel> Genres { get; set; }
}
and in your view you would have:
#Html.EditorFor(x => x.MovieTitle)
#Html.EditorFor(x => x.Genres)
Another option is to either use the TextBox(string name, object value) overload instead of the TextBoxFor:
#Html.TextBox("Title", Model.Movie.Title)
You could also specify the input tag HTML instead of using a helper.
Another option is to take EditVM as your postback parameter. This is what I would do. My post action parameter is always the same type of the .cshtml model. Yes there will be properties like lists that are null, but you just ignore those. It also allows you to gracefully handle post errors as well because if there is an error you'll need to return an instance of that view model anyhow, and have the values they submitted included. I usually have private methods or DB layer that handles retrieving the various lists that go into the ViewModel, since those will be empty on postback and will need to be repopulated, while not touching the properties that were in the post.
With your post method as it is now, if you need to return the same view, you've gotta create a new EditVM and then copy any posted values into it, and still populate the lists. With my method, you eliminate one of those mapping steps. If you are posting more than one thing, are you going to have umpteen different parameters on your post action? Just let them all come naturally into a single parameter typed to the EditVM of the View. While maybe having those null properties in the VM during the postback feels icky, you get a nice predictable consistency between View and postback IMO. You don't have to spend alot of time thinking about what combination of parameters on your post method will get you all the pieces of data from the form.

How to get currently executing area?

I have a class used by controllers at [Project].Controllers and by controllers at different areas. How could I determine where the controller is at? (I guess I could look at the HttpContext.Current.Request's properties -but I am looking for a "proper" MVC way). Thank you.
That is:
[Project].Helpers // called by:
[Project].Controllers
[Project].Areas.[Area].Controllers
// how could I determine the caller from [Project].Helpers?
We purposefully did not expose a way to get the current area name from an MVC request since "area" is simply an attribute of a route. It's unreliable for other uses. In particular, if you want your controllers to have some attribute (think of the abstract term, not the System.Attribute class) which can be used by the helper, then those attributes must be found on the controllers themselves, not on the area.
As a practical example, if you want some logic (like an action filter) to run before any controllers in a particular area, you must associate the action filter with those controllers directly. The easiest way to do this is to attribute some MyAreaBaseController with that filter, then to have each controller that you logically want to associate with that area to subclass that type. Any other usage, such as a global filter which looks at RouteData.DataTokens["area"] to make a decision, is unsupported and potentially dangerous.
If you really, really need to get the current area name, you can use RouteData.DataTokens["area"] to find it.
You should be able to get the area string from RouteData:
// action inside a controller in an area
public ActionResult Index()
{
var area = RouteData.DataTokens["area"];
....
return View();
}
.. so you can make an extension method for helpers like this:
public static class SomeHelper // in [Project].Helpers
{
public static string Area(this HtmlHelper helper)
{
return (string)helper.ViewContext.RouteData.DataTokens["area"];
}
}

ASP.NET StrongTyped Controller-Action View<TView,TModel>(TModel Data)

I'm using Asp.net MVC 1 and I would really like my controller actions to use StronglyTyped View(data) calls that enforce type checking at compile time and still let me use aspx pages under the default view engine. The ViewPages I call are strongly typed, but errors in the action's call to View(data) can't be caught at compile time because the built in Controller View(data) method isn't strongly typed and doesn't even check to see if the page exists at compile time.
I've implemented a partial solution (code below) using this post but (1) I can't get the generic View function to recognize the Type of strong view pages unless I create a code behind for the strongly typed view, and (2) Intellisense and refactoring don't work properly with this method which makes me doubt the reliability of the method I'm using.
Question:
Is there a better way to get type enforcement when calling Views from actions?
Alternative: Is there an alternative method where my action method can create an instance of a viewpage, set some properties directly and then render out its HTML to the action response?
Code:
Here's the base Class all my Controllers Inherit from to achieve what I have so far:
public class StrongController : Controller
{
protected ActionResult View<TView, TModel>(TModel model)
where TView : ViewPage<TModel>
where TModel : class
{
return View(typeof(TView).Name, model);
}
}
And here's an example Controller in use:
namespace ExampleMVCApp.Controllers
{
public class HomeController : StrongController
{
public ActionResult Index()
{
return View<ExampleMVCApp.Views.Home.Index, ExampleData>(new ExampleData());
}
}
}
ViewPage Code Behind Required for Type Recognition... Aspx header didn't work
namespace ExampleMVCApp.Views.Home
{
public class Issue : System.Web.Mvc.ViewPage<ExampleData>
{
}
}
I think you should give the T4MVC helpers a spin (one of the original announcements here). This would at least enable you to get rid of the code you already have, since these templates generate the code based on the Views you already have and you employ these "fake" method calls to address your views.
For having your calls to View to be strongly typed for the specific model declared by your view, I am not exactly sure if these helpers help you with that (though I suspect they do). However, if they don't you can still hack the T4MVC code to do so yourself or get in touch with the original author, David Ebbo, to suggest the feature for addition.

using ViewModels for POST actions in MVC elegantly

Currently I'm passing my domain objects to my views, and binding directly to them from POSTs. Everyone says this is bad, so I'm attempting to add in the ViewModel concept.
However, I can't find a way to do this very elegantly, and I'd like to know what other people's solutions are to not ending up with a very messy controller action.
the typical process for say some "add person" functionality looks like this:
make a GET request for a view representing a blank Person viewmodel
post back (in)valid data
controller binds posted data onto a person viewmodel
if binding fails, i need to do the same action as in (1) but with some data, not a blank object and errors
if the binding suceeded, i need to map the properties from the VM onto a real model
validate the model
if validation passed: save the person, commit, map the users details to a display VM and return it in a view
if validation failed, do the same actions as in (1) but with some data and errors
Doing all this in a controller action (ignoring the GET) certainly isnt SRP or DRY.
Im trying to think of a way of breaking this process up so that it does abide by SRP, is clean, modular and above all testable.
What are peoples solution to this?
I've been experimenting with custom controller-action-invokers to separate the concerns up into individual methods, smart modelbinders and just plain brute force but i havent yet come across a solution in happy with.
P.S. as it adds so much complexity, convince me why i even need to bother
I've felt the same discomfort. My only way around it has been to do the following:
Create a binder to bind and validate the view model
Create a binder to get the entity from the database (or just do this in the controller)
Call an inherited Save method in the superclass. This method takes the viewmodel and the entity that will be updated, and does all the work you listed in your steps.
The action method looks like this:
public ActionResult Whatever(TViewModel viewModel, TEntity entity)
{
return Save(viewModel, entity);
}
The base controller has a generic definition, like so:
public abstract BaseController<TEntity, TViewModel>
where TEntity : Entity
where TViewModel : ViewModel
The constructor has two dependencies, one for the entity repository and another for the model mapper, like so:
protected BaseController(IRepository<TEntity> repository, IMapper<TEntity, TViewModel> mapper)
With this in place, you can then write a protected Save method that can be called from the controller actions in the subclass, like so:
protected ActionResult Save(TViewModel viewModel, TEntity entity)
{
if (!ModelState.IsValid)
return View(viewModel);
_mapper.Map(viewModel, entity);
if (!entity.IsValid)
{
// add errors to model state
return View(viewModel);
}
try
{
_repository.Save(entity);
// either redirect with static url or add virtual method for defining redirect in subclass.
}
catch (Exception)
{
// do something here with the exception
return View(viewModel);
}
}
As far as testability, you can test the save method passing in valid/invalid view models and entities. You can test the implementation of the model mapper, the valid state of the view model, and the valid state of the entity separately.
By making the base controller generic, you can repeat this pattern for each entity/viewmodel combo in your domain, if you're creating many controllers to do the same thing.
I'm very interested to hear what others have to say about this. Great question.
The MVVM (ViewModel) pattern is definitely the one to go for, I had a similar question about POSTing back to an action a few days back - here is the link: MVVM and ModelBinders in the ASP.NET MVC Framework
The result was that you can use the Bind attribute to post back the complex type you want.
I have many good solutions in the asp.net mvc sample application which is in the download of valueinjecter (mapper that I use to map ViewModels to/from Entities, you can also map FormCollection/Request to Entities)
here's one:
public class TinyController :Controller
{
private readonly IModelBuilder<Person, PersonViewModel> modelBuilder;
public TinyController()
{
modelBuilder = new PersonModelBuilder();
}
public ActionResult Index()
{
return View(modelBuilder.BuildModel(new PersonRepository().Get()));
}
[HttpPost]
public ActionResult Index(PersonViewModel model)
{
if (!ModelState.IsValid)
return View(modelBuilder.RebuildModel(model));
var entity = modelBuilder.BuildEntity(model);
...
//save it or whatever
}
}

Resources