HTTP GET to return custom model with data from external database with Umbraco MVC Surface Controller - asp.net-mvc-4

I am currently working on an Umbraco MVC 4 project version 6.0.5. The project currently uses Vega.USiteBuilder to build the appropriate document types in the backoffice based on strongly typed classes with mapping attributes. Consequently, all my razor files inherit from UmbracoTemplatePageBase
I am coming across a road block trying to invoke a HTTP GET from a razor file. For example a search form with multiple fields to submit to a controller action method, using a SurfaceController using Html.BeginUmbracoForm.
My Html.BeginUmbracoForm looks like this
#using (Html.BeginUmbracoForm("FindTyres", "TyreSearch"))
{
// Couple of filter fields
}
I basically have a scenario where I will like to retrieve some records from an external database outside of Umbraco (external to Umbraco Database) and return the results in a custom view model back to my Umbraco front end view. Once my controller and action method is setup to inherit from SurfaceController and thereafter compiling it and submitting the search, I get a 404 resource cannot be found where the requested url specified: /umbraco.RenderMVC.
Here is my code snippet:
public ActionResult FindTyres(string maker, string years, string models, string vehicles)
{
var tyreBdl = new Wheels.BDL.TyreBDL();
List<Tyre> tyres = tyreBdl.GetAllTyres();
tyres = tyres.Where(t => string.Equals(t.Maker, maker, StringComparison.OrdinalIgnoreCase)
&& string.Equals(t.Year, years, StringComparison.OrdinalIgnoreCase)
&& string.Equals(t.Model, models, StringComparison.OrdinalIgnoreCase)
&& string.Equals(t.Version, vehicles, StringComparison.OrdinalIgnoreCase)).ToList();
var tyreSearchViewModel = new TyreSearchViewModel
{
Tyres = tyres
};
ViewBag.TyreSearchViewModel = tyreSearchViewModel;
return CurrentUmbracoPage();
}
I then resort to using standard MVC, Html.BeginForm (the only difference). Repeating the steps above and submitting the search, I get the following YSOD error.
Can only use UmbracoPageResult in the context of an Http POST when
using a SurfaceController form
Below is a snippet of the HTML BeginForm
#using (Html.BeginForm("FindTyres", "TyreSearch"))
{
// Couple of filter fields
}
I feel like I am fighting the Umbraco routes to get my controller to return a custom model back to the razor file. I have googled alot trying to figure out how to do a basic search to return a custom model back to my Umbraco front end view till the extent that I tried to create a custom route but that too did not work for me.
Does my controller need to inherit from a special umbraco controller class to return the custom model back? I will basically like to invoke a HTTP GET request (which is a must) so that my criteria search fields are reflected properly in the query strings of the url. For example upon hitting the search button, I must see the example url in my address browser bar
http://[domainname]/selecttyres.aspx/TyresSearch/FindTyresMake=ASIA&Years=1994&Models=ROCSTA&Vehicles=261
Therefore, I cannot use Surface Controller as that will operate in the context of a HTTP Post.
Are there good resource materials that I can read up more on umbraco controllers, routes and pipeline.
I hope this scenario makes sense to you. If you have any questions, please let me know. I will need to understand this concept to continue on from here with my project and I do have a deadline.

There are a lot of questions about this and the best place to look for an authoritative approach is the Umbraco MVC documentation.
However, yes you will find, if you use Html.BeginUmbracoForm(...) you will be forced into a HttpPost action. With this kind of functionality (a search form), I usually build the form manually with a GET method and have it submit a querystring to a specific node URL.
<form action="#Model.Content.Url"> ... </form>
On that page I include an #Html.Action("SearchResults", "TyresSearch") which itself has a model that maps to the keys in the querystring:
[ChildAction]
public ActionResult(TyreSearchModel model){
// Find results
TyreSearchResultModel results = new Wheels.BDL.TyreBDL().GetAllTyres();
// Filter results based on submitted model
...
// Return results
return results;
}
The results view just need to have a model of TyreSearchResultModel (or whatever you choose).
This approach bypasses the need for Umbraco's Controller implementation and very straightforward.

I have managed to find my solution through route hijacking which enabled me to return a custom view model back to my view and work with HTTP GET. It worked well for me.
Digby, your solution looks plausible but I have not attempted at it. If I do have a widget sitting on my page, I will definitely attempt to use your approach.
Here are the details. I basically override the Umbraco default MVC routing by creating a controller that derived from RenderMvcController. In a nutshell, you implement route hijacking by implementing a controller that derives from RenderMvcController and renaming your controllername after your given documenttype name. Recommend the read right out of the Umbraco reference (http://our.umbraco.org/documentation/Reference/Mvc/custom-controllers) This is also a great article (http://www.ben-morris.com/using-umbraco-6-to-create-an-asp-net-mvc-4-web-applicatio)
Here is my snippet of my code:
public class ProductTyreSelectorController : Umbraco.Web.Mvc.RenderMvcController
{
public override ActionResult Index(RenderModel model)
{
var productTyreSelectorViewModel = new ProductTyreSelectorViewModel(model);
var maker = Request.QueryString["Make"];
var years = Request.QueryString["Years"];
var models = Request.QueryString["Models"];
var autoIdStr = Request.QueryString["Vehicles"];
var width = Request.QueryString["Widths"];
var aspectRatio = Request.QueryString["AspectRatio"];
var rims = Request.QueryString["Rims"];
var tyrePlusBdl = new TPWheelBDL.TyrePlusBDL();
List<Tyre> tyres = tyrePlusBdl.GetAllTyres();
if (Request.QueryString.Count == 0)
{
return CurrentTemplate(productTyreSelectorViewModel);
}
if (!string.IsNullOrEmpty(maker) && !string.IsNullOrEmpty(years) && !string.IsNullOrEmpty(models) &&
!string.IsNullOrEmpty(autoIdStr))
{
int autoId;
int.TryParse(autoIdStr, out autoId);
tyres = tyres.Where(t => string.Equals(t.Maker, maker, StringComparison.OrdinalIgnoreCase) &&
string.Equals(t.Year, years, StringComparison.OrdinalIgnoreCase) &&
string.Equals(t.Model, models, StringComparison.OrdinalIgnoreCase) &&
t.AutoID == autoId)
.ToList();
productTyreSelectorViewModel.Tyres = tyres;
}
else if (!string.IsNullOrEmpty(width) && !string.IsNullOrEmpty(aspectRatio) && !string.IsNullOrEmpty(rims))
{
tyres = tyres.Where(t => string.Equals(t.Aspect, aspectRatio, StringComparison.OrdinalIgnoreCase) &&
string.Equals(t.Rim, rims, StringComparison.OrdinalIgnoreCase)).ToList();
productTyreSelectorViewModel.Tyres = tyres;
}
var template = ControllerContext.RouteData.Values["action"].ToString();
//return an empty content result if the template doesn't physically
//exist on the file system
if (!EnsurePhsyicalViewExists(template))
{
return Content("Could not find physical view template.");
}
return CurrentTemplate(productTyreSelectorViewModel);
}
}
Note my ProductTyreSelectorViewModel must inherit from RenderModel for this to work and my document type is called ProductTyreSelector. This way when my model is returned with the action result CurrentTemplate, the Umbraco context of the page is retained and my page is rendered appropriately again. This way, all my query strings will show all my search/filter fields which is what I want.
Here is my snippet of the ProductTyreSelectorViewModel class:
public class ProductTyreSelectorViewModel : RenderModel
{
public ProductTyreSelectorViewModel(RenderModel model)
: base(model.Content, model.CurrentCulture)
{
Tyres = new List<Tyre>();
}
public ProductTyreSelectorViewModel(IPublishedContent content, CultureInfo culture)
: base(content, culture)
{
}
public ProductTyreSelectorViewModel(IPublishedContent content)
: base(content)
{
}
public IList<Tyre> Tyres { get; set; }
}
This approach will work well perhaps with one to two HTTP GET forms on a given page. If there are multiple forms within in a page, then a good solution will may be to use ChildAction approach. Something I will experiment with further.
Hope this helps!

Related

Is it possible to have a function in a controller act as if the controller has the [ApiController] attribute but it has not?

I have a .net core MVC project that uses AJAX to retrieve data when loading grids.
Today, both the function returning the view and the function returning the data for the grid, is in the same controller. This is not optimal for many reasons. I.e. I would like a Json ProblemResult to be returned if a exception occurs when calling function using AJAX, but when returning a View, I would like the Developer Exception page to be shown if an error occurs.
I could split the functions into different Controllers and annotate one of them with the ApiController attribute, but since the project has several hundreds of controllers it would be a significant task to do so.
What I would like is this:
If context type is application/json: Do model validation and return a “ProblemResult” if a exception occurs, otherwise use the Developers Exception Page to show the error.
Can this be done in a easy way, or do I need to build a middleware and handle it all by myself?
The easy way, meaning, doing this from a function that returns either a view or a type of JSON result would look something like this:
public IActionResult AjaxOrView(CheckModel model)
{
var isAjax = Request.Headers["X-Requested-With"] == "XMLHttpRequest";
var modelStateValid = ModelState.IsValid;
if (isAjax)
{
if (!modelStateValid)
{
return JsonProblemResult();
}
return Json();
}
if (!modelStateValid)
{
// this will throw and the exception page will be shown
throw new Exception();
}
return View();
}

Efficient way to bring parameters into controller action URL's

In ASP.Net Core you have multiple ways to generate an URL for controller action, the newest being tag helpers.
Using tag-helpers for GET-requests asp-route is used to specify route parameters. It is from what I understand not supported to use complex objects in route request. And sometimes a page could have many different links pointing to itself, possible with minor addition to the URL for each link.
To me it seems wrong that any modification to controller action signature requires changing all tag-helpers using that action. I.e. if one adds string query to controller, one must add query to model and add asp-route-query="#Model.Query" 20 different places spread across cshtml-files. Using this approach is setting the code up for future bugs.
Is there a more elegant way of handling this? For example some way of having a Request object? (I.e. request object from controller can be put into Model and fed back into action URL.)
In my other answer I found a way to provide request object through Model.
From the SO article #tseng provided I found a smaller solution. This one does not use a request object in Model, but retains all route parameters unless explicitly overridden. It won't allow you to specify route through an request object, which is most often not what you want anyway. But it solved problem in OP.
<a asp-controller="Test" asp-action="HelloWorld" asp-all-route-data="#Context.GetQueryParameters()" asp-route-somestring="optional override">Link</a>
This requires an extension method to convert query parameters into a dictionary.
public static Dictionary GetQueryParameters(this HttpContext context)
{
return context.Request.Query.ToDictionary(d => d.Key, d => d.Value.ToString());
}
There's a rationale here that I don't think you're getting. GET requests are intentionally simplistic. They are supposed to describe a specific resource. They do no have bodies, because you're not supposed to be passing complex data objects in the first place. That's not how the HTTP protocol is designed.
Additionally, query string params should generally be optional. If some bit of data is required in order to identify the resource, it should be part of the main URI (i.e. the path). As such, neglecting to add something like a query param, should simply result in the full data set being returned instead of some subset defined by the query. Or in the case of something like a search page, it generally will result in a form being presented to the user to collect the query. In other words, you action should account for that param being missing and handle that situation accordingly.
Long and short, no, there is no way "elegant" way to handle this, I suppose, but the reason for that is that there doesn't need to be. If you're designing your routes and actions correctly, it's generally not an issue.
To solve this I'd like to have a request object used as route parameters for anchor TagHelper. This means that all route links are defined in only one location, not throughout solution. Changes made to request object model automatically propagates to URL for <a asp-action>-tags.
The benefit of this is reducing number of places in the code we need to change when changing method signature for a controller action. We localize change to model and action only.
I thought writing a tag-helper for a custom asp-object-route could help. I looked into chaining Taghelpers so mine could run before AnchorTagHelper, but that does not work. Creating instance and nesting them requires me to hardcode all properties of ASP.Net Cores AnchorTagHelper, which may require maintenance in the future. Also considered using a custom method with UrlHelper to build URL, but then TagHelper would not work.
The solution I landed on is to use asp-all-route-data as suggested by #kirk-larkin along with an extension method for serializing to Dictionary. Any asp-all-route-* will override values in asp-all-route-data.
<a asp-controller="Test" asp-action="HelloWorld" asp-all-route-data="#Model.RouteParameters.ToDictionary()" asp-route-somestring="optional override">Link</a>
ASP.Net Core can deserialize complex objects (including lists and child objects).
public IActionResult HelloWorld(HelloWorldRequest request) { }
In the request object (when used) would typically have only a few simple properties. But I thought it would be nice if it supported child objects as well. Serializing object into a Dictionary is usually done using reflection, which can be slow. I figured Newtonsoft.Json would be more optimized than writing simple reflection code myself, and found this implementation ready to go:
public static class ExtensionMethods
{
public static IDictionary ToDictionary(this object metaToken)
{
// From https://geeklearning.io/serialize-an-object-to-an-url-encoded-string-in-csharp/
if (metaToken == null)
{
return null;
}
JToken token = metaToken as JToken;
if (token == null)
{
return ToDictionary(JObject.FromObject(metaToken));
}
if (token.HasValues)
{
var contentData = new Dictionary();
foreach (var child in token.Children().ToList())
{
var childContent = child.ToDictionary();
if (childContent != null)
{
contentData = contentData.Concat(childContent)
.ToDictionary(k => k.Key, v => v.Value);
}
}
return contentData;
}
var jValue = token as JValue;
if (jValue?.Value == null)
{
return null;
}
var value = jValue?.Type == JTokenType.Date ?
jValue?.ToString("o", CultureInfo.InvariantCulture) :
jValue?.ToString(CultureInfo.InvariantCulture);
return new Dictionary { { token.Path, value } };
}
}

Switching MVC view on Post back using strongly typed views/view models

User requests page for Step1, fills out and submits form that contains selected person, so far so good. After validation of ModelState the next viewmodel is constructed properly using the selected person. I then attempt a redirect to action using the newVM but find on entry to Step2 that MVC wipes out the viewmodel attempted to be passed in. I suspect this is due to how MVC attempts to new up and instance based on query string results. I'll put a breakpoint in and check that, but am wondering how does one change a view from a post back with a new view model passed in?
public ActionResult Step1()
{
var vm = new VMStep1();
return View(vm);
}
[HttpPost]
public ActionResult Step1(VMStep1 vm)
{
if (ModelState.IsValid)
{
var newVM = new VMStep2(vm.SelectedPerson);
return RedirectToAction("Step2", newVM);
}
return View(vm);
}
public ActionResult Step2(VMStep2 vm)
{
return View(vm);
}
I can fix this by containing VMStep2 and a partial to Step2 in Step1 view, but that requires hide and seek logic when really I just want user to see Step2.
I don't see why you should want to call RedirectToAction! What it does it the following:
it tells your browser to redirect and like it or not your browser doesn't understand how to handle your object -- what it does understand is JSON. So if you really insist on using return RedirectToAction("Step2", newVM); you should consider a way to serialize your VMStep2 object to JSON and when the browser requests the Redirect, it will be properly passed and created in your action method public ActionResult Step2(VMStep2 vm)
HOWEVER I'd use a much simpler way ---
instead of
return RedirectToAction("Step2", newVM);
I would use
return View("Step2", newVM);
Thanks to everyone for the great input!
Here's what I did...
I created three views MainView, Step1View, Step2View (Step 1 and 2 were partial strong typed views)
I created a MainViewModel that contained VMStep1 and VMStep2
When controller served Step1 the MainViewModel only initialized VMStep1 and set state logic to tell MainView Step1 was to be shown.
When user posted back the MainView containing the MainViewModel, the MainViewModel knew what to do by the answers provided in VMStep1.
VMStep2 was initialized on the post back, and state was set to tell MainView to show Step2. VMStep1 was no longer relevant and was set to null.
User was now able to answer using VMStep2 and all was well.
The key to this working is that some flag tells the view which partial to show, the partial takes a model supporting it's strong type which is initialized at the right time. End result is fast rendering and good state machine progression.

Specifying route values for OData with Web API

I'm working on a new OData project, and am trying to do it with Web API 2 for the first time. The OData feed was pretty simple to put in place, which was great in comparison to WCF.
The problem I have now is that my OData feed will be used in a "multi-tenant" environment and I would like to use "friendly" URLs for the feed depending on the tenant. Therefore, I would ideally need the feed URLs to look like this:
/store/tenant1/Products
/store/tenant2/Products
Both URLs are pointing to the same controller and ultimately the same dataset, but I would like to enforce some entity filtering based on the tenant. Apparently this is going to be difficult and somewhat different to standard Web API routing since I can only specify a route prefix and not a route template.
So far, I've modified my OData controller to take the tenant name as a parameter and this works great when hitting the following url (which is not exactly what I want, see target above):
http://mydomainname/odata/Products?tenantName=test
Using this route definition:
ODataConventionModelBuilder modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Product>("Products");
IEdmModel model = modelBuilder.GetEdmModel();
config.Routes.MapODataRoute(routeName: "OData", routePrefix: "odata", model: model);
And this is the sample action on my controller:
[Queryable]
public IQueryable<Product> GetPproducts(string tenantName)
{
return _products.Where(p=>p.TenantName == tenantName);
}
I'm not quite sure if this is possible and my last resort will be to use URL rewrite rules, but I'd rather avoid this and have everything in code, done the right way.
Thanks a lot for your help!
After some investigation I found it works in this way: Just apply the route prefix name to the query, for example:
public class MoviesController : ODataController
{
private MoviesContext _db = new MoviesContext();
public IHttpActionResult Get()
{
var routeName=Request.ODataProperties().RouteName;
ODataRoute odataRoute=Configuration.Routes[routeName] as ODataRoute;
var prefixName = odataRoute.RoutePrefix;
return Ok(_db.Movies.Where(m=>m.Title.StartsWith(prefixName)));
}
// Other methods here
}
Note: The above code is based on ODataActionsSample in https://aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/OData/v4/
Now OData v4 has become a standard of OASIS, but v3 is not, so v4 seems a good start point.

How to keep your MVC controllers DRY for Edit->Save->ValidateFail

I've got a Manage User event that takes an an optional userID and displays a user edit screen. There is a manageUserViewModel to go with this screen.
My Manage page has some dependencies - eg, PageTitle, what method to submit to, etc.
If I validate-fail, I need to show the manage screen again, but this time, using the view-model that was passed into the same method.
Supplying these dependencies in the fail scenario isn't very DRY.
How do I step repeating the dependencies? I tried putting them into a separate method, but that does not feel right.
public ActionResult Manage(Guid? UserID)
{
User user = this._UserLogic.GetUser(UserID);
ViewBag.Title = "User List";
ViewBag.OnSubmit = "Save";
ManageUserViewModel uvm = Mapper.Map<User, ManageUserViewModel>(user);
return View("Manage", uvm);
}
[AcceptVerbs("POST")]
public ActionResult Save(ManageUserViewModel uvm)
{
User user = this._UserLogic.GetUser(uvm.UserID);
if (!ModelState.IsValid)
// This is not very DRY!!!
ViewBag.Title = "Manage User";
ViewBag.OnSubmit = "Save";
return View("Manage", uvm);
}
Mapper.Map<ManageUserViewModel, User>(uvm, user );
this._UserLogic.SaveUser(user);
return RedirectToAction("Manage", new { UserID = user.ID });
}
I think you misunderstand DRY. DRY does not mean "NEVER repeat yourself", it means that you should not repeat yourself when it makes sense not to.
Different views have different requirements, and creating a complex structure just to avoid repeating yourself violates other best practices, like KISS, and SRP.
SOLID is interesting because Single Responsibility Principle is often at odds with Don't Repeat Yourself, and you have to come up with a balance. In most cases, DRY loses because SRP is far more important.
It looks to me like you have code here that is handling multiple responsibilities just so you can avoid writing similar code more than once. I disagree with doing that, because each view has different responsibilities and different requirements.
I would suggest just creating separate controller actions, views, and models for each action, particularly if the validation requirements are different for them. There may be a few things you can do (like using Partial Views or Editor Templates) to reduce repetition, but in general don't add lots of complexity just to avoid repetition.
You could add the 'Manager User' Title and 'Save' OnSubmit strings as properties of on the ManageUserViewModel. This means that you would not have to add them to the ViewBag each time you called Save.
You could also make a ManageUserService which could be responsible for the AutoMapper mappings and saving the user.
You code would then look like this:
public ActionResult Manage(Guid? UserID)
{
var uvm = _userService.GetById(UserId);
return View("Manage", uvm);
}
[AcceptVerbs("POST")]
public ActionResult Save(ManageUserViewModel uvm)
{
if (!ModelState.IsValid)
{
return View("Save", uvm);
}
_userService.Save(uvm);
return RedirectToAction("Manage", new { UserID = uvm.ID });
}
Just put the CRUD logic and AutoMapping functionality in the a class called UserService, and instance of which can be injected using Inversion of Control into your controller.
If you don't want to hard-code your string values into the view model itself, then you could add the values to an ApplicationResources file and reference those from the view model.
You will have to find some way to preserve this information between requests, which either means passing it back and forth between the client and server or saving it on the server. Saving it on the server means something like session but this feels a little heavy to me. You could add it to your ViewModel as #Ryan Spears suggested. To me that feels a little wrong, polluting the ViewModel with something that might be considered metadata. But that is just an opinion and I am not discrediting his answer because it is valid. Another possibility would be to just add the extra fields to the parameter list of the action method itself and use hidden fields.
[AcceptVerbs("POST")]
public ActionResult Save(ManageUserViewModel uvm, string title, string onSubmit)
{
...
}
In the form add:
<input type="hidden" name="title" value="#ViewBag.Title" />
<input type="hidden" name="onSubmit" value="#ViewBag.OnSubmit" />
This is essentially the same concept and solution as adding them to the ViewModel except in this situation they are not actually part of the ViewModel.
You can use RedirectToAction() and then export and import your tempdata (to maintain the ModelState) if you're worried about the 3 lines.
Personally I'd find it a lot more readable if you kept the logic in the POST version of the method, as you're performing something slightly different from the GET method, therefore not really repeating yourself. You could you probably keep the two ViewBag variables you have inside the View, and then there's no repetition at all.
As a side note: [HttpPost] now supersedes [AcceptVerbs]
We have come up with another solution that I thought I would share.
This based on the view-model containing info on what actions it can do, but we feel the controller should be specifying these (ie, controlling what actions different links route to) these because we have cases where the view-models are reused across actions. EG, the case where when you edit you can edit a template or an instance of something - the UI is the same, the only difference is the actions you post to/cancel from.
We abstracted away the part of the view-model that contains the data bound properties and the view model that contains other things we need for the view to render. We call the property-only object a DTO - it's not a true dto because it contains validation attributes.
We figure that we might be able to re-use these DTO's in the future for ajax or even XML requests - it, can keep validation DRY.
Anyway - here is an example of the code, we are happy with it (for now) and hope it helps others.
[HttpGet]
[ValidateInput(false)]
public virtual ActionResult ManageUser(ManageUserDTO dto, bool PopulateFromObject = true)
{
User user = this._UserLogic.GetUser(dto.UserID);
if (PopulateFromObject)
Mapper.Map<User, ManageUserDTO>(user, dto);
ManageUserViewModel vm = new ManageUserViewModel()
{
DTO = dto,
PageTitle = Captions.GetCaption("pageTitle_EditUser"),
OnSubmit = GetSubmitEventData(this.ControllerName, "SaveUser"),
OnCancel = GetCancelEventData(this.ControllerName, "ListUsers"),
};
return View("ManageUser", vm);
}
[HttpPost]
public virtual ActionResult SaveUser(ManageUserViewModel vm)
{
User user = this._UserLogic.GetUser(vm.DTO.UserID);
if (!ModelState.IsValid)
{
return ManageUser(vm.DTO, false);
}
Mapper.Map<ManageUserDTO, User>(vm.DTO, user);
this._UserLogic.SaveUser(user);
TempData.AddSuccess(Captions.GetCaption("message_UserSavedSuccessfuly"));
return RedirectToAction("ManageUser", new { UserID = user.ID });
}
The model-binder will set any URI variables into the dto in the get action. My logic layer will return a new User object if a call to getUserByID(null) is made.