I have a model that contains an Address property and Latitude/Longitude properties. The model is populated in a "Create" view, where they put in most of the other fields. I populate the Lat/Lng using the Google Maps API geocoding functionality based on the entered address (no sense in making them put this in manually).
My big question is where should this be done. The code below works, but I think it's pretty clunky. Any thoughts on a better way to integrate this behavior? Should it be in the controller? Should it be part of some internal model mechanism?
[HttpPost]
public ActionResult Create(Church church)
{
try
{
if (ModelState.IsValid)
{
string address = string.Format("{0},{1},{2} {3}",
church.Street + church.Street2, church.City,
church.Region, church.PostalCode);
JObject jsonResult = GoogleApiHelper.GetAddressGeocodeData(address);
//Handle some error states here...
if (jsonResult["results"].Count() == 1)
{
church.Latitude = jsonResult.SelectToken(
"results[0].geometry.location.lat").Value<double>();
church.Longitude = jsonResult.SelectToken(
"results[0].geometry.location.lng").Value<double>();
unitOfWork.ChurchRepository.Insert(church);
unitOfWork.Save();
return RedirectToAction("Index");
}
}
}
catch (DataException)
{
//Log the error (add a variable name after DataException)
ModelState.AddModelError("", "Unable to save changes.");
}
return View(church);
}
You could use a repository layer to abstract the access to the Google API and then invoke this repository from the controller to give you an already populated model. This way if tomorrow you decide to use Bing instead of Google all you have to change is the implementation of the repository. No need to touch your controller or model logic.
You could write a custom model binder and put all of your Google API code in there. That way your controller will be completely oblivious to your data access.
Here is a tutorial on custom model binders:
http://buildstarted.com/2010/09/12/custom-model-binders-in-mvc-3-with-imodelbinder/
The main question is are you going to create churches anywhere else?
If not then this is a perfectly acceptable place to put the code.
If you are then put the code into a ChurchService which can be called from both places. That way you can Keep to DRY (Don't Repeat Yourself) principles without over complicating your code.
Related
Right, so a) I'm not sure if this is the right forum for this question, I failed to find a better suggestion, and b) I couldn't find an answer to a question I can't believe I haven't asked before:
So if I have a model, standard form to fill in and it errors so I throw it back to the user, how do I, or how SHOULD I, structure the code to make sure all the supporting properties are constructed?
Example:
public ActionResult DoThing()
{
var model = new DoThingModel();
model.Things = ThingService.FetchThings();
//Do Other Things
return View(model);
}
The fairly standard GET action
[HttpPost]
public ActionResult DoThing(DoThingModel model)
{
var valid = ThingService.ValidateThing(model);
if(!valid)
{
model.ValidationMessage = "Ohh no validation failed";
model.valid = false;
return View(model); //I have't rebuilt model.Things with ThingService.FetchThings()
}
else
{
return RedirectToAction("Success");
}
}
So if the thing is valid redirect to the success page, but otherwise return the form (contained within the model) to the user with a validation message. Problem is my form page has things on it and I haven't populated the model with it.
Now, this is a very simple example and I could just pass back things in the form, or duplicate the line of code that populates it. But imagine I have a much more complicated model, with breadcrumbs, multiple properties that need populating. Code duplication = bad, so how SHOULD I be making sure the model has everything I need before passing it back? I thought about creating a service but it's a service with one function. I thought about using a constructor, but that doesn't play well with model binding. I thought about creating a DoThingModel.Init() that does all the work, but putting code like that inside a model class seems dirty. I played around with a private function in the Controller that filled in all the required properties, but this seems dirty again.
I feel like there should be a standard code pattern that goes => Create New Model => Make sure Model has everything that supports the page => return Model => validate Model => re-apply any properties => return Model => repeat until valid.
Like I said in the intro, this must be a standard thing somewhere, but I can't find it.
I have a registration page in my application. It has 3 states and 1 error state(If any error comes):
Fill Basic Information
Select Package
Say Thanks
Error
Now I want to use state pattern here. First I created a console application which is OK. Now I want to implement this logic in my MVC application but I am confused about the structure. I mean how many views, models and controller I need and where to place my logic.
1 controller: RegistrationController
6 action methods:
GET+POST for Index (fill in basic info)
GET+POST for Package
GET for Thank you
GET for Error
This is rough code to make your mind going:
public class RegistrationController : Controller
{
public ActionResult Index()
{
RegistrationState model = RegistrationState.Init();
// just display the "Fill Basic Info" form
return View(model);
}
[HttpPost]
public ActionResult Index(RegistrationState data)
{
// process data and redirect to next step
this.TempData["RegState"] = data;
if (!this.ModelState.IsValid || data.State == State.Error)
{
// error should handle provided state and empty one as well
return RedirectToAction("Error");
}
return RedirectToAction("Package");
}
public ActionResult Package()
{
RegistrationState data = this.TempData["RegState"] as RegistrationState;
if (data == null)
{
return RedirectToAction("Error");
}
// get packages and display them
IList<Package> model = this.repository.GetPackages();
return View(new Tuple.Create(data, model));
}
[HttpPost]
public ActionResult Package(RegistrationState data)
{
// process data blah blah blah
}
// and so on and so forth
....
}
As you can see you still have to write some MVC-related code to act upon state changes. In my example everything's done in action methods. But action filters could be used as well. If you can't come up with a general action filter that can serve many different state objects then it's best to just write the code in action methods.
Another approach
If you know Asp.net MVC good enough you could take this a step further and write a state machine ControllerFactory that would work along with routing in a sense as:
{StateObjectType}/{State}
ControllerFactory would therefore be able to parse view data to a known state object type and pass execution to particular action. According to state. This would make it a specially state machine suited Asp.net MVC application.
The more important question is of course whether you can create the whole application with this pattern or are there just certain parts of it that should work like this. You could of course combine both approaches and provide appropriate routing for each.
Important notices
You should be very careful how you define your error state, because entering invalid field data shouldn't result in error state but rather in data validation errors that actually display within the view beside the field with invalid data (ie. invalid date provided as 13/13/1313). Your error state should only be used for actual object state error that's not related to user input. What would that be is beyond my imagination.
As mentioned in my comment you should check out some Asp.net MVC intro videos and you'll see how validation works in Asp.net MVC. Also rather simple stuff.
State pattern of this kind is not something a regular Asp.net MVC developer would use, because it would most likely complicate code more than taking the normal approach. Analyse before you decide. Asp.net MVC is very clean code wise so adding additional abstraction over it may become confusing. And your domain model (state classes) would most likely have a much more complex code as simple POCOs with data annotations.
In your case data validation would also be more complicated (when used with data annotations) because you object should be validated according to its state which may be different between states. POCO objects are always validated the same. This may mean that we may use more classes but they are smaller, simpler and easier to maintain.
I think you are confusing states. Examples of state are:
Awaiting for a user to register
User registered successfully
User didn't register successfully
Now each of these states would have a page:
localhost:8034/Register
localhost:8034/Register/Success
localhost:8034/Register/Failure
If user can't register because they left some fields empty, they will be in the first state and you will have to display some validation messages.
Because of this, as the minimum I'll have a controller called Register and the following action methods:
Index() GET/POST
Success() GET
Failure() GET
So in order accomplish what I asked in this post I did the following:
[iPhone]
[ActionName("Index")]
public ActionResult IndexIPhone()
{
return new Test.Areas.Mobile.Controllers.HomeController().Index();
}
[ActionName("Index")]
public ActionResult Index()
{
return View();
}
Which still serves the same view as the Index action method in this controller. Even though I can see it executing the Test.Areas.Mobile.Controllers.HomeController().Index() action method just fine. What's going on here? And how do I serve the Index view from Mobile area without changing the request URL (as asked in the original post referenced above)?
You have a few options:
Redirect to the Action you'd like to return: return RedirectToAction("Action-I-Want").
Return the View by name: return View("The-View-I-Want").
Note that with the 2nd approach you'd have to put your view in the "Shared" folder for all controllers to be able to find it and return it. This can get messy if you end up putting all your views there.
As a side note: The reason your work doesn't find the view is because default view engine looks for the view in the folder that "belongs" to the current executing controller context, regardless of what code you're calling.
Edit:
It is possible to group all "mobile" views in the same folder. On your Global.asax (or where ever you're setting up your ViewEngine, just add the path to your mobile View in the AreaViewLocationFormats. Mind you, you'll still have to name your views differently.
You can also write your own view engine. I'd do something like detecting the browser and then serving the right file. You could setup a convention like View.aspx, and View.m.aspx.
Anyhow, just take a look at WebFormViewEngine and you'll figure out what works best for you.
The easiest way to send a request to a view handled by another controller is RedirectToAction("View-Name", "Controller-Name").
There are overloads of View() that take route information that might work as well, but they'd require more effort to set up.
Well actually the easiest way is to make one version of your site programmed on standards instead of browser detection :D -- however in direct response to accomplish what it in a more of a ASP.NET mvc fashion, using:
RedirectToAction("ViewName", "ControllerName");
is a good method however I have found it is more practical if you feel you must program for different browser standards to create a primary view and an alternate "mobile" view under your controllers views. Then instead of writing special code on every controller, instead extend the controller like so.
public class ControllerExtended : Controller
{
private bool IsMobile = false;
private void DetectMobileDevices(){ .... }
}
Then modify your controller classes to instead say ControllerExtended classes and just add the one line to the top of each Action that you have alternate views of like so:
public class ApplicationsController : ControllerExtended
{
// GET: /Applications/Index
public ActionResult Index() {
this.DetectMobileDevices();
if(this.IsMobile){
return RedirectToAction("MobileIndex");
} else {
// actual action code goes here
return View();
}
}
}
Alternately you can use return View("ViewName"); but from my experience you want to actually perform different actions as opposed to just showing the result in a different view as in the case of presenting an HTML table as opposed to a Flex table to help iPhone users since there is no flash support in the iPhone, etc. (as of this writing)
I'm relatively new to view models and I'm running into a few problems with using them. Here's one situation where I'm wondering what the best practice is...
I'm putting all the information a view needs into the view model. Here's an example -- please forgive any errors, this is coded off the top of my head.
public ActionResult Edit(int id)
{
var project = ProjectService.GetProject(id);
if (project == null)
// Something about not found, possibly a redirect to 404.
var model = new ProjectEdit();
model.MapFrom(project); // Extension method using AutoMapper.
return View(model);
}
If the screen only allows editing of one or two fields, when the view model comes back it's missing quite a bit of data (as it should be).
[HttpPost]
public ActionResult Edit(int id, ProjectEdit model)
{
var project = ProjectService.GetProject(id);
if (project == null)
// Something about not found, possibly a redirect to 404.
try
{
if (!ModelState.IsValid)
return View(model) // Won't work, view model is incomplete.
model.MapTo(project); // Extension method using AutoMapper.
ProjectService.UpdateProject(project);
// Add a message for the user to temp data.
return RedirectToAction("details", new { project.Id });
}
catch (Exception exception)
{
// Add a message for the user to temp data.
return View(model) // Won't work, view model is incomplete.
}
}
My temporary solution is to recreate the view model from scratch, repopulate it from the domain model, reapply the form data to it, then proceed as normal. But this makes the view model parameter somewhat pointless.
[HttpPost]
public ActionResult Edit(int id, ProjectEdit model)
{
var project = ProjectService.GetProject(id);
if (project == null)
// Something about not found, possibly a redirect to 404.
// Recreate the view model from scratch.
model = new ProjectEdit();
model.MapFrom(project); // Extension method using AutoMapper.
try
{
TryUpdateModel(model); // Reapply the form data.
if (!ModelState.IsValid)
return View(model) // View model is complete this time.
model.MapTo(project); // Extension method using AutoMapper.
ProjectService.UpdateProject(project);
// Add a message for the user to temp data.
return RedirectToAction("details", new { project.Id });
}
catch (Exception exception)
{
// Add a message for the user to temp data.
return View(model) // View model is complete this time.
}
}
Is there a more elegant way?
EDIT
Both answers are correct so I would award them both if I could. The nod goes to MJ however since after trial and error I find his solution to be the leanest.
I'm still able to use the helpers too, Jimmy. If I add what I need to be displayed to the view bag (or view data), like so...
ViewBag.Project= project;
I can then do the following...
#Html.LabelFor(model => ((Project)ViewData["Project"]).Name)
#Html.DisplayFor(model => ((Project)ViewData["Project"]).Name)
A bit of a hack, and it requires the domain model to be decorated with System.ComponentModel.DisplayNameAttribute in some cases, but I already do that.
I'd love to call...
#Html.LabelFor(model => ViewBag.Project.Name)
But dynamic causes a problem in expressions.
After some trial-and-error (aka code it, then hate it) learning, my currently preferred approach is:
I use view-models only to bind input fields. So in your case, if your view is only editing two fields, then your view-model will only have two properties. For the data required to populate the view (drop-down lists, labels, etc), I use the dynamic ViewBag.
I believe that displaying the view (i.e. populating anything the view needs to display), and capturing the posted form values (binding, validation, etc) are two separate concerns. And I find that mixing the data required to populate the view with that which is posted back from the view gets messy, and creates exactly your situation more often than not. I dislike partially populated objects being passed around.
I’m not sure how this plays out with Automapper (for mapping the domain object to the dynamic ViewBag) though, as I haven’t used it. I believe it has a DynamicMap method that may work? You shouldn’t have any issues auto-mapping the posted strongly-typed ViewModel onto the Domain object.
If I understand correctly, your viewmodel probably looks very similar to your domain entity. You mentioned that the viewmodel can come back mostly empty because only certain fields were editable.
Assuming you have a view where only a few fields are available for edit (or display), these are the only fields you should make available in your viewmodel. I usually create one viewmodel per view, and let either the controller or a service handle the user's input and map it back up with the domain entity after performing some validation.
Here's a thread concerning best-practices for viewmodels that you might find useful.
Edit: You can also accept a different view model in your Edit/POST action than your Edit/GET action serves up. I believe this should work as long as the model binder can figure it out.
For each of my business entities I have a corresponding view model.
I have a generic CRUD controller that works like this:
[HttpPost]
public virtual ActionResult Create(TViewModel model, int? id)
{
// Validate input
if (!ModelState.IsValid)
return Json(Failure(createView, model.SelectLists(repository)));
// Prepare Model
var entity = new TModel();
// Add to repository
UpdateModel(entity);
repository.Add(entity);
repository.Save();
return Json(CreateSuccess(entity));
}
I use data annotations on my view model properties and this works great for simple input validation.
Now I have a case where I want to make sure a duplicate record isn't created by accident.
My first instinct is to put this logic in the repository's Add method. This implementation would be easy, but how do I get the repository to add a model state error and return some useful information to the user? I feel like there has to be a solution out there, but I haven't had much luck searching.
Thanks for any help!
I would use exceptions.
Throw your custom application exception at the Add method, if an enity is doubled.
Wrap the Add method in a try block to catch this specific exception at the Create method.
Add a model state error based on the exception data at the catch block
try
{
repository.Add(entity);
}
catch(MyRepositoryException ex)
{
ViewData.ModelState.AddModelError(ex.Key, ex.Value.ToString(), ex.Message)
}
if (!ModelState.IsValid)
return Json(Failure(createView, model.SelectLists(repository)));
An alternative to your approach would be to use the idea of a ModelStateWrapper implementing IValidationDictionary. It basically decouples the modelState, but still lets your repository/service interact with errors dictionary. This way error handling is all done via an interface and there's no need to reference any MVC-specific data object.
There's a good writeup on it here: http://www.asp.net/mvc/tutorials/validating-with-a-service-layer-cs, but the basic idea is:
1) Pass an instance of your ModelStateWrapper to your repository during the controller's initialization:
public MyController()
{
repository = new MyRepository(new ModelStateWrapper(this.ModelState));
}
2) Add errors to it inside your repository:
_validatonDictionary.AddError("Name", "Name is required.");
3) Handle errors like you normally would in your controller:
if (!repository.Save())
return View();
I hate answering my own questions, but I think I stumbled across the answer I was looking for while searching for something else:
http://nerddinnerbook.s3.amazonaws.com/Part3.htm
Looks like it was time for a back-to-basics review! I should have thought to go back and review my first tutorial as there was no way I was absorbing everything when I was first starting out.
Part 3 of the tutorial talks about implementing domain model validation that returns errors with property name and error message strings that are to be added to the controller's ModelState, which allows for this kind of validation:
if (ModelState.IsValid) {
try {
dinner.HostedBy = "SomeUser";
dinnerRepository.Add(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new{id=dinner.DinnerID});
}
catch {
ModelState.AddModelErrors(dinner.GetRuleViolations());
}
}
I don't know if I like the idea of raising exceptions for business rule violations, but the basic pattern will work well for my project. Hope this helps someone else!