How should I design MVC to conditionally return JSON or pretty HTML? - asp.net-mvc

I have data that will either be consumed by a human or a web service. The REST url is:
http://host.com/movies/detail/1
http://host.com/movies/detail/The Shawshank Redemption (1994)
What convention should I follow to conditionally return JSON or HTML? Should I add a parameter such as "?json" or should I look at the client headers,.. some variation of both?
If I do a variation of both, if a conflict is found which takes precedent?

Check whether the Request is Ajax. You may use the Request.IsAjaxRequest() method which returns true/false.
public ActionResult details(string id)
{
var movieViewModel=movieService.GetMovieDetails(id);
If(Request.IsAjaxRequest())
{
// return Json now
return Json(movieViewModel,JsonRequestBehavior.AllowGet);
}
// Not an ajax request, Let's return Normal View (HTML)
return View(movieViewModel);
}
UNIT TESTING ASPECT : Request.IsAjaxRequest() is not unit test friendly! So if you are worried about unit tests, You can write your IsAjaxRequest property and put in your basecontroller class and use it.
public class BaseController : Controller
{
protected bool IsAjaxRequest()
{
//Using this method instead of Request.IsAjaxRequest() because
//IsAjaxRequest is static and not mockable ! (not unit test friendly)
var isAjax = Request.Headers["X-Requested-With"];
if (isAjax != null && isAjax.ToUpper() == "XMLHTTPREQUEST")
return true;
return false;
}
}
Now inherit your controller from this BaseController.
public class HomeController : BaseController
{
public ActionResult details(string id)
{
var movieViewModel=movieService.GetMovieDetails(id);
If(IsAjaxRequest)
{
// return Json now
return Json(movieViewModel,JsonRequestBehavior.AllowGet);
}
// Not an ajax request, Let's return Normal View (HTML)
return View(movieViewModel);
}
}

You could also use:
[HttpGet]
public ActionResult() {
// Return HTML
}
[HttpPost]
public ActionResult() {
// Assuming Ajax is of the type post
}
Just another solution if all your Ajax is using post.

I prefer using a parameter explicit in the URL because that way building REST petitions is easy for developers, self explanatory and with no surprises (do you have to guess default format? or see "difficult" to see HTTP headers). You decide:
if you have many options for formats you can use format=json
you can go with json parameter, but it is not pretty because you have to pair it with a value json=true, json=1. Besides you can set json=1&xml=1&html=1, harder to handle.
the twitter way is to emulate an extension such as call.json or call.xml (e.g. https://dev.twitter.com/docs/api/1.1/get/statuses/user_timeline)
I recommend don't tie together a kind of petition and a format. Let your API clients decide, ajax-json is commonly used, but not all develepers use it that way. Maybe I am writing a terminal application with no ajax, maybe I want to do a wget to your API and still get json.

Related

MVC application: json + view output

I developed an mvc-razor app the classical way: I do some computation in my controller and show the results in the view.
Now, in addition to the formatted output I already provide through the view, I need to add the ability to provide the same results in json, so that my controller acts as a service.
Something like
if (json == true)
{
return JsonOutput(model);
}
else
{
return View(model);
}
Since I don't want to reinvent the wheel, I wonder if there is a standard approach to this task.
Thank you
What you are looking for is an ApiController:
[Route("api/[controller]")]
[ApiController]
public class EmployeesController : ControllerBase { ... }
In this controller you will define your action methods, very much like in Mvc, but not return a View:
[HttpGet]
public async Task<IActionResult> Get()
{
/* ... retrieve results*/
return Ok(yourPayload);
}
I suggest you follow along the excellent documentation: https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-5.0&tabs=visual-studio

ASP.NET MVC 4, how to access/modify the view model object (and change view and action method) before it is used as action method parameter?

Is there any useful hook in ASP.NET MVC (MVC4) which can let you access the Action method parameter (View model) before the action method becomes invoked, and then also (e.g. depending on the value of something you checked in the action method parameter) let you prevent the action method from being invoked, i.e. instead either forward the view model object (action method parameter) to another action method or directly to some view (i.e. without any further processing in an action method) ?
If you do not understand the question, please see the code example below which should illustrate the kind of code I am looking for...
(though I do not know if there actually exists such kind of interface and a possibility to hook an implementation into the MVC framework)
If this is indeed possible, I would like to see an answer with code example about how to do it (and not just a response with someone claiming that e.g. "try using method 'ActionFilterAttribute.OnActionExecuting' or 'IModelBinder.BindModel' " because I have already tried those and could not make it work).
Also, please respect that I do not want this thread to become a discussion about WHY to do it, but want to see HOW to do it.
(i.e. I am not interested in getting into discussions with responses such as "What are you actually trying to achieve?" or "There are probably better things of doing what you want to do...")
The question can be split into three subquestions/code examples as my own code samples below try to illustrate:
(but would like them "refactored" into REAL code with usage of real existing types)
(obviously, every type below which includes the substring "Some" is something I have made up, and I am looking for the corresponding real thing ...)
(1) Example of how to get access to (and potentially modify) view model objects (action method parameters) in a generic place before the actual action method is invoked with the view model object parameter.
The kind of code example I am looking for would probably be similar to below but do not know what kind of interface to use and how to register it to be able to do something like below:
public class SomeClass: ISomeInterface { // How to register this kind of hook in Application_Start ?
public void SomeMethodSomewhere(SomeActionMethodContext actionMethodContext, object actionMethodParameterViewModel) {
string nameOfTheControllerAboutToBeInvoked = actionMethodContext.ControllerName;
string nameOfTheActionMethodAboutToBeInvoked = actionMethodContext.MethodName;
// the above strings are not used below but just used for illustrating that the "context object" contains information about the action method to become invoked by the MVC framework
if(typeof(IMyBaseInterfaceForAllMyViewModels).IsAssignableFrom(actionMethodParameterViewModel.GetType())) {
IMyBaseInterfaceForAllMyViewModels viewModel = (IMyBaseInterfaceForAllMyViewModels) actionMethodParameterViewModel;
// check something in the view model:
if(viewModel.MyFirstGeneralPropertyInAllViewModels == "foo") {
// modify something in the view model before it will be passed to the target action method
viewModel.MySecondGeneralPropertyInAllViewModels = "bar";
}
}
}
}
(2) Example of how to prevent the targeted action method from being executed and instead invoke another action method.
The example might be an extension of the above example, with something like below:
public void SomeMethodSomewhere(SomeActionMethodContext actionMethodContext, object actionMethodParameterViewModel) {
... same as above ...
if(viewModel.MyFirstGeneralPropertyInAllViewModels == "foo") {
actionMethodContext.ControllerName = "SomeOtherController";
actionMethodContext.MethodName = "SomeOtherActionMethod";
// The above is just one example of how I imagine this kind of thing could be implemented with changing properties, and below is another example of doing it with a method invocation:
SomeHelper.PreventCurrentlyTargetedActionMethodFromBecomingExecutedAndInsteadExecuteActionMethod("SomeOtherController", "SomeOtherActionMethod", actionMethodParameterViewModel);
// Note that I do _NOT_ want to trigger a new http request with something like the method "Controller.RedirectToAction"
}
(3) Example of how to prevent the normal action method from being executed and instead forward the view model object directly to a view without any further processing.
The example would be an extension of the first above example, with something like below:
public void SomeMethodSomewhere(SomeActionMethodContext actionMethodContext, object actionMethodParameterViewModel) {
... same as the first example above ...
if(viewModel.MyFirstGeneralPropertyInAllViewModels == "foo") {
// the below used razor view must of course be implemented with a proper type for the model (e.g. interface 'IMyBaseInterfaceForAllMyViewModels' as used in first example above)
SomeHelper.PreventCurrentlyTargetedActionMethodFromBecomingExecutedAndInsteadForwardViewModelToView("SomeViewName.cshtml", actionMethodParameterViewModel);
}
You could use an action filter and override the OnActionExecuting event:
public class MyActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
...
}
}
Now let's see what useful information you could extract from this filterContext argument that is passed to this method. The property you should be looking for is called ActionParameters and represents an IDictionary<string, object>. As its name suggests this property contains all the parameters that are passed to the controller action by name and value.
So let's suppose that you have the following controller action:
[MyActionFilter]
public ActionResult Index(MyViewModel model)
{
...
}
Here's how you could retrieve the value of the view model after model binding:
public class MyActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var model = filterContext.ActionParameters["model"] as MyViewModel;
// do something with the model
// You could change some of its properties here
}
}
Now let's see the second part of your question. How to shortcircuit the controller action and redirect to another action?
This could be done by assigning a value to the Result property:
public class MyActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
... some processing here and you decide to redirect:
var routeValues = new RouteValueDictionary(new
{
controller = "somecontroller",
action = "someaction"
});
filterContext.Result = new RedirectToRouteResult(routeValues);
}
}
or for example you decide to shortcircuit the execution of the controller action and directly render a view:
public class MyActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var viewResult = new ViewResult
{
ViewName = "~/Views/FooBar/Baz.cshtml",
};
MyViewModel someModel = ... get the model you want to pass to the view
viewResult.ViewData.Model = model;
filterContext.Result = viewResult;
}
}
or you might decide to render a JSON result:
public class MyActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
MyViewModel someModel = ... get the model you want to pass to the view
filterContext.Result = new JsonResult
{
Data = model,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
}
So as you can see the possibilities are unlimited of what you can do.
I have experimented with the code in the answer provided by the user Darin Dimitrov, and the first and third parts of the answer are correct.
(Though, for others who might find this thread and be interested, I can clarify that in the first answer the "model" does not seem to
be a hardcoded keyword always used for the model but seems to have to correspond to the chosen name of the action method parameter.
In other words, if you instead have the method signature
public ActionResult Index(MyViewModel myViewModel)
then in your action filter you have to use
var model = filterContext.ActionParameters["myViewModel"] as MyViewModel;
)
Regarding the second answer, the usage of 'RedirectToRouteResult' will trigger a new http request (which was not desired as I mentioned in the second code example of mine).
I found another way of "changing" action method by actually invoking it explicitly:
var controller = new SomeController();
ActionResult result = controller.SomeAction(model);
filterContext.Result = result;
The above code actually seems to prevent the originally targeted action method from becoming invoked, i.e. when I put a breakpoint in the method annotated with '[MyActionFilter]' the execution never got into that method.
Typically, it is probably not desired to hardcode a controller like above, but instead reflection might be used, for example as below with the thirdpart library "fasterflect":
string nameOfController = ...
string nameOfActionMethod = ...
// both above variables might for example be derived by using some naming convention and parsing the refering url, depending on what you want to do ...
var theController = this.GetType().Assembly.CreateInstance(nameOfController);
ActionResult result = (ActionResult)theController.CallMethod(nameOfActionMethod, model);
filterContext.Result = result;
(for those who want to extract the names of the current target controller and action method, when implementing logic to determine the controller you want to invoke, you can use this code in the filter:
var routeValueDictionary = filterContext.RouteData.Values;
string nameOfTargetedController = routeValueDictionary["controller"].ToString();
string nameOfTargetedActionMethod = routeValueDictionary["action"].ToString();
)
I think it feels a bit awkward to instantiate and invoke controllers like above, and would prefer to change the target controller and action method in another way if possible ?
So, the remaining question is if there is still (in MVC 4 final version) no way of redirecting/forwarding execution "internally" (without a new http request being fired as with 'RedirectToAction') at the server ?
Basically, I think I am here just looking for something like "Server.Transfer" which was used with ASP.NET Web Forms (and also the old classic ASP I believe could use the same thing).
I have seen older question/answers on this issue with people implementing this behaviour themselves with some "TransferResult" class of their own, but it seems to tend to become broken i different MVC versions.
(for example, see here for MVC 4 beta: How to redirect MVC action without returning 301? (using MVC 4 beta) ).
Is there really still not a simple standard solution (implemented in MVC 4 final) about how to do an "internal redirect" without a new http request (as RedirectToAction does) ?

How to make ASP.NET MVC Action return different formats?

I don't know if this is the right way to approach something, but I'm hoping it is. The example below is a heavy controller and is absolutely the wrong approach, but it get's the idea of what I'm looking for across.
public class PeopleController : Controller
{
public ActionResult List(string? api)
{
MyViewModel Model = new MyViewModel();
if (api == "json") {
// I'd like to return the Model as JSON
} else if (api == "XML") {
// I'd like to return the Model as XML
} else {
return View(Model);
}
}
}
Now what I need to be able to do is return the Model to the View if it's being requested like this:
http://example.com/People/List
But I'd like it to output JSON if it's requested like this:
http://example.com/People/List/?api=json
Or output XML if it's requested like this:
http://example.com/People/List/?api=xml
Is this just plain wrong? If not, what is the best approach to achieve this?
I was thinking of achieving it with a Custom MultiPurposeResult that could do all the filtering for me and then return it as this
public class PeopleController : Controller
{
public MultiPurposeResult List(string? api)
{
MyViewModel Model = new MyViewModel();
return MultiPurpose(Model); }
}
}
Agree with #Matt. Don't explicitly ask for the response type, infer it from the contentType in the request, which is more RESTful.
For example, created a basic enum type to encapsulate the response types you want:
public enum RestfulResultType
{
Json,
Html,
Xml
}
Then create a custom model binder than sets this property in your action, depending on the content type.
Then your controller could look like this:
public ActionResult List(RestfulResultType resultType)
{
var data = repo.GetSomeData();
switch (resultType)
{
case RestfulResultType.Json:
return Json(data);
case RestfulResultType.Xml:
return XmlResult(data); // MvcContrib
case RestfulResultType.Html:
return View(data);
}
}
If you need any more customization than the regular helpers provide, then create custom ActionResult's.
You can leave the return type as ActionResult - that's the point, so that the controller can return different formats.
ResfulResultTypeModelBinder.cs:
public class ResfulResultTypeModelBinder: IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (controllerContext.HttpContext.Request.ContentType == "application/json")
return RestfulResultType.Json;
// other formats, etc.
}
}
Global.asax:
ModelBinders.Binders.Add(typeof(RestfulResultType), new RestfulResultTypeModelBinder());
You can create a custom MultiPurposeResult but I personally would lose the string? api from the method signature, instead have the MultiPurpose look for the presence of Request["format"] and then make the determination of what format to possible output the results in. Since the format doesn't nessecarily have anything to do with the ActionResult but more the format of the response.

if a controller action redirects to external url, what do you return in the function?

i have a function and i am unclear what i should return from this?
public ActionResult LoadExternalURL()
{
Response.Redirect("http://www.google.com");
// what do i return here ??
}
Instead of calling Response.Redirect it's easier to use the built in RedirectResult ActionResult as follows:-
return Redirect("http://www.google.com");
This will also improve the testability of your code (you don't have to muck around mocking the HTTP context) and instead you can just test the Url property of the returned action result.

Best way to unit test ASP.NET MVC action methods that use BindingHelperExtensions.UpdateFrom?

In handling a form post I have something like
public ActionResult Insert()
{
Order order = new Order();
BindingHelperExtensions.UpdateFrom(order, this.Request.Form);
this.orderService.Save(order);
return this.RedirectToAction("Details", new { id = order.ID });
}
I am not using explicit parameters in the method as I anticipate having to adapt to variable number of fields etc. and a method with 20+ parameters is not appealing.
I suppose my only option here is mock up the whole HttpRequest, equivalent to what Rob Conery has done. Is this a best practice? Hard to tell with a framework which is so new.
I've also seen solutions involving using an ActionFilter so that you can transform the above method signature to something like
[SomeFilter]
public Insert(Contact contact)
I'm now using ModelBinder so that my action method can look (basically) like:
public ActionResult Insert(Contact contact)
{
if (this.ViewData.ModelState.IsValid)
{
this.contactService.SaveContact(contact);
return this.RedirectToAction("Details", new { id = contact.ID });
}
else
{
return this.RedirectToAction("Create");
}
}
Wrap it in an interface and mock it.
Use NameValueDeserializer from http://www.codeplex.com/MVCContrib instead of UpdateFrom.

Resources