How to make ASP.NET MVC Action return different formats? - asp.net-mvc

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.

Related

Get access to posted values in custom modelbinder

Currently I have an IActionFilter that accepts a List<T> as parameter. In that action method I examine the posted viewmodel values. It looks something like this:
[HttpPost]
public async Task<IActionResult> SavePage(List<BaseField> fields)
{
for (var i = 0; i < fields.Count; i++)
{
if (fields[i].Type == "bb")
{
var inputObj = new InputConfigViewModel();
await TryUpdateModelAsync(inputObj, $"fields[{i}]");
}
if (fields[i].Type == "ee")
{
var tObj = new TextareaConfigViewModel();
await TryUpdateModelAsync(tObj, $"fields[{i}]");
}
}
return RedirectToAction("Index", "Dashboard");
}
This works so far. But I would like to abstract this code away to a custom ModelBinder class.
public class BaseFieldModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
// Need access to "List<BaseField> fields"...
return Task.CompletedTask;
}
}
How can I get access the List<BaseField> fields values in my ModelBinder, like I can from the action method in my Controller?
Custom model binders work on objects and not generic lists. You cannot access the all list inside the binder, but you can access each individual object.
With this said, I don't believe you can abstract the code, because you don't have the ControllerContext you need to access the FormCollection inside the binder context, neither to execute the TryUpdateModelAsync call in order to get extra information from form post that is not present on the List<BaseField>. You just have the ModelBindingContext.
If you still want to try, you have a good working example here.
Here is the relevant part, where you can get the reference of the object:
// Try to fetch the value of the argument by name
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None)
{
return TaskCache.CompletedTask;
}
bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);
var value = valueProviderResult.FirstValue;
// Check if the argument value is null or empty
if (string.IsNullOrEmpty(value))
{
return TaskCache.CompletedTask;
}

Implementing data interface in ASP.NET MVC Controller and returning JSON

I would like to have my Controller implement my data interface that returns custom objects. All the objects are serializable and decorated by JSON attributes. So I would like to have my controller method simply be that:
public class MyController : Controller, IMyInterface
{
public Foo[] GetFoosByName(string name)
{
return new Foo[]{new Foo(name), new Foo(name)}
}
}
If I do it I get simply "Foo[]" response. What I'd like to get is JSON-serialized Foo objects.
I can easily achieve this by changing my response to be JsonResult:
public JsonResult GetFoosByName(string name)
{
return Json(new Foo[]{new Foo(name), new Foo(name)});
}
But then I won't be able to implement IMyInterface in the way that is easily maintainable.
Any ideas on how I can automatically get the behavior as I was returning JsonResult, but still keeping original return types?
Generally I would recommend against such a pattern. An MVC Controller should ideally be kind of a top-most layer, and I think it should not implement interfaces. You should implement such interfaces in a service layer, below the MVC Controllers.
However, if you still want to do it so, you can use explicit implementation like this.
public class MyController : Controller, IMyInterface
{
public JsonResult GetFoosByName(string name)
{
return Json(((IMyInterface)this).GetFoosByName(name));
}
Foo[] IMyInterface.GetFoosByName(string name)
{
return new[] { new Foo(name) };
}
}

How should I design MVC to conditionally return JSON or pretty HTML?

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.

How to convert DTO to View Model and then back again? [duplicate]

This question already has answers here:
Where to convert business model to view model?
(3 answers)
Closed 5 years ago.
I'm using MVC 4 with the repository pattern and unit testing also. I have a typical controller that has simple CRUD functionality. I've separated my View Models from my DTOs and I would like to know the best way to convert between the 2:
Models:
I have Admin.Models.Product which is my view model and AdminAssembly.Models.Product which is my DTO.
Controller:
//repo that handles product operations
AdminAssembly.Interfaces.IEntityRepository<AdminAssembly.Models.Product> db;
//default constructor
public ProductController() { db = new AdminAssembly.Repositories.EntityRepo<AdminAssembly.Models.Product>(new AdminAssembly.Models.EntitiesContext()); }
//unit testing constructor
public ProductController(AdminAssembly.Interfaces.IEntityRepository<AdminAssembly.Models.Product> context) { db = context; }
//
// POST: /Product/Create
[HttpPost]
public ActionResult Create(Admin.Models.Product product) {
if (ModelState.IsValid) {
//COMPILE-ERROR: how to convert to DTO?
db.Add(product);
}
return View();
}
//
// GET: /Product/Edit/5
public ActionResult Edit(int id) {
//COMPILE-ERROR: how to convert to view model?
Admin.Models.Product product = db.GetAll().Where(p => p.ID == id);
return View(product);
}
How do I convert between the 2?
Do I reference my DTO assembly in my view model and do something like: (won't this break my unit testing?)
//convert to AdminAssembly.Models.Product
db.Add(product.ToDTO());
//convert back to Admin.Models.Product via constructor
Admin.Models.Product product = Admin.Models.new Product(db.GetAll().Where(p => p.ID == id));
Do I need some sort of object conversion black box?
Converter.ToViewProduct(product);
Some sort of interface?
or something else?
Update 1:
public static class Product {
public static Admin.Models.Product ToView(AdminAssembly.Models.Product dto) {
Admin.Models.Product viewProduct = new Admin.Models.Product();
//straight copy
viewProduct.Property1 = dto.Property1;
viewProduct.Property2 = dto.Property2;
return viewProduct;
}
public static AdminAssembly.Models.Product ToDTO(Admin.Models.Product viewModel) {
AdminAssembly.Models.Product dtoProduct = new AdminAssembly.Models.Product();
//straight copy
dtoProduct.Property1 = viewModel.Property1;
dtoProduct.Property2 = viewModel.Property2;
//perhaps a bit of wizza-majig
dtoProduct.Property1 = viewModel.Property1 + viewModel.Property2;
return dtoProduct;
}
}
The long-hand response
[HttpPost]
public ActionResult Create(Admin.Models.Product product)
{
if (ModelState.IsValid)
{
//COMPILE-ERROR: how to convert to DTO?
var dtoProduct = new AdminAssembly.Models.Product();
dtoProduct.Property1 = product.Property1;
dtoProduct.Property2 = product.Property2;
//...and so on
db.Add(dtoProduct);
}
return View();
}
While this looks verbose and tedious (and it is) it has to happen eventually, somewhere.
You can hide this mapping either in another class or extension method, or you can use a third party like AutoMapper, as Charlino points out.
As a side note, having two classes with the same name in two different namespaces will eventually get confusing (if not for you, then for the next person who has to maintain your code.) Implement friendlier and more descriptive names wherever possible. For example, put all your view models in a folder called ViewModels, not Models. And append all your view models with ViewModel, or VM. It's also a good convention, imo, to name your view models based on the view that they are for, not so much the domain model that they will be mapped to, as not all view models will map directly to a domain model. Sometimes you'll want parts of more than one domain model, for a single view, and that will blow up your naming convention.
So in this particular case I would suggest changing Admin.Models to Admin.ViewModels and then rename the view model version of Product to CreateViewModel. Your code will be much more readable and will not be littered with namespaces throughout your methods.
All of that would result in a method that would look more like this:
[HttpPost]
public ActionResult Create(CreateViewModel viewModel)
{
if (ModelState.IsValid)
{
var product = new Product();
product.Property1 = viewModel.Property1;
product.Property2 = viewModel.Property2;
//...and so on
db.Add(product);
}
return View();
}
Check out a library called AutoMapper.
From their wiki:
What is AutoMapper?
AutoMapper is a simple little library built to solve a deceptively complex problem - getting rid of code that mapped one object to another. This type of code is rather dreary and boring to write, so why not invent a tool to do it for us?
If you dont want to use AutoMapper you may use extensions, as suggested by #Forty-Two. If the number of things to map is no very great, I would go with this approach, just because then, AutoMapper == YAGNI
public static class Extensions
{
public static ViewModel ToViewModel(this Model )
{
var vm = new ViewModel()
{
//map
};
return vm;
}
public static Model ToModel(this ViewModel viewModel)
{
var model = new Model()
{
//map
};
return model;
}
}
Similar to your code in UPDATE, but using extensions instead.

ASP.NET MVC Posting models to an action with an an Interface

My understanding is that ASP.NET MVC only allows you to POST objects to Actions in the Controller, where the Action's arguments accept the posted object as a Concrete class.
Is there any way around this, or a good alternative?
In my case, I have an action which accepts an interface as an argument:
public ActionResult SaveAdjustment(IModel model)
{
switch (model.SubsetType)
{
// factory like usage
}
}
And for this action, I have numerous views, all strongly typed to objects that implement IModel, all which I want to be able to post to this one method.
Of course, running this give me the error:
Cannot create an instance of an interface
Is there a nice work around to this? Or do I need to create an Action method for each and send them over to a method like this?
MVC generally binds models when posting from Request.Form, that is collection of name=value pairs. The reason that in default implementation there's no support of binding interfaces or abstract classes is obvious - mvc cannot determine which concrete class to create from name=value pairs. If you got hidden field on client side, or any other parameter anywhere by which you are able to determine which type of concrete class to create, you can simply create custom model binder. I believe you can override DefaultModelBinder's CreateModel method and reuse all other built in binding functionality
public class IModelModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, System.Type modelType)
{
//Create and return concrete instance
}
}
And model binder registration in global.asax
ModelBinders.Binders.Add(typeof(IModel?), new IModelModelBinder());
Actually, controllers and actions in mvc are meant to be thin, and some kind of service layer should be thick. As action logic you are trying to implement may get complicated soon, I would recommend moving it into separate service.
Although I mentioned this as a possible solution in my original question, its the solution I have gone with in the end and I actually quite like it now. This way I didn't need to touch the model default binding implementation and I think this approach is a more readable/understandable approach than what I was originally asking for.
In case its not clear why I wanted to go for this approach, I have added an example of how I can use this for its OO benifits.
[HttpPost]
public ActionResult SaveModelA(ModelA model)
{
return SaveModel(model);
}
[HttpPost]
public ActionResult SaveModelB(ModelB model)
{
return SaveModel(model);
}
private ActionResult SaveModel(IModel model)
{
IExampleService exampleService;
IRequirements requirements;
switch (model.SubsetType)
{
case SubsetType.ModelA:
myService = new ModelAService();
requirements = new ModelARequirements
{
ModelASpecificProperty = "example"
};
break;
case SubsetType.ModelB:
myService = new ModelBService();
requirements = new ModelBRequirements
{
ModelBSpecificProperty1 = "example",
ModelBSpecificProperty2 = "example2",
ModelBSpecificProperty3 = "example3"
};
break;
default:
throw new InvalidEnumArgumentException();
}
var serviceResonse = exampleService.ExecuteExample(model, requirements);
return RedirectToAction("Index", new
{
ExampleData = serviceResponse.ExampleDate
});
}
In case it isn't clear in the code:
ModelA : IModel
ModelB : IModel
ModelARequirements : IModelRequirements
ModelBRequirements : IModelRequirements
ModelAService : IExampleService
ModelBService : IExampleService
// and IModel defines a property SubsetType SubsetType { get; }

Resources