MVC3 REST service - how do I access the request body content for a PUT or POST request? - asp.net-mvc-3

I am creating an ASP.NET MVC3 restful web service to allow reports to be uploaded from a set of servers. When a new report is created, I want the client app to do a PUT to
http://MyApp/Servers/[ServerName]/Reports/[ReportTime]
passing the content of the report as XML in the body of the request.
My question is: how do I access the content of the report in my controller? I would imagine that it is available somewhere in the HttpContext.Request object but I am reluctant to access that from my controller as it is not possible(?) to unit test that. Is it possible to tweak the routing to allow the content to be passed as one or more parameters into the controller method? The outcome needs to be RESTful, i.e. it has to PUT or POST to a URL like the one above.
Currently my routing is:
routes.MapRoute(
"SaveReport",
"Servers/{serverName}/Reports/{reportTime",
new { controller = "Reports", action = "Put" },
new { httpMethod = new HttpMethodConstraint("PUT") });
Is there any way to modify this to pass content from the HTTP request body into the controller method?
The controller method is currently:
public class ReportsController : Controller
{
[HttpPut]
public ActionResult Put(string serverName, string reportTime)
{
// Code here to decode and save the report
}
}
The object I am trying to PUT to the URL is:
public class Report
{
public int SuccessCount { get; set; }
public int FailureOneCount { get; set; }
public int FailureTwoCount { get; set; }
// Other stuff
}
This question looks similar but doesn't have any answer.
Thanks in advance

Seems like you just need to use the standard ASP.NET MVC model binding capability with the slight wrinkle that you would doing an HTTP PUT instead of the more common HTTP POST. This article series has some good samples to see how model binding is used.
Your controller code would then look like:
public class ReportsController : Controller
{
[HttpPut]
public ActionResult Put(Report report, string serverName, string reportTime)
{
if (ModelState.IsValid)
{
// Do biz logic and return appropriate view
}
else
{
// Return invalid request handling "view"
}
}
}
EDIT: ====================>>>
Jon added this code to his comment as part of the fix so I added it to the answer for others:
Create a custom ModelBinder:
public class ReportModelBinder : IModelBinder
{
public object BindModel(
ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
var xs = new XmlSerializer(typeof(Report));
return (Report)xs.Deserialize(
controllerContext.HttpContext.Request.InputStream);
}
}
Modify the Global.asax.cs to register this model binder against the Report type:
ModelBinders.Binders[typeof(Report)] = new Models.ReportModelBinder();

Related

ASP.NET Web API - how to pass unknown number of form-encoded POST values

The front-end of my application can send unknown number of POST values inside a form. Fro example in some cases there will be 3 values coming from certain textboxes, in some cases there will be 6 values coming from textboxes, dropdowns etc. The backend is ASP.NET Web API. I know that a simple .NET value can be passed in URI parameter to a "POST Action" using FromURI attribute and a complex type can be passed in body and fetched using FromBody attribute, in any POST Action. But in my case the number of form data values will NOT be constant rather variable and I can't use a pre-defined class to hold values using 'FromBody' attribute.
How can I tackle this situation?
You can use the FormDataCollection from the System.Net.Http.Formatting namespace.
public class ApiFormsController : ApiController
{
[HttpPost]
public IHttpActionResult PostForm(FormDataCollection form)
{
NameValueCollection items = form.ReadAsNameValueCollection();
foreach (string key in items.AllKeys)
{
string name = key;
string val = items[key];
}
return Ok();
}
}
Try to send this properties as list of properties. Make model something like this:
public class PostModel
{
public IEnumerable<PropertyModel> Properties { get; set; }
}
public class PropertyModel
{
public string Value { get; set; }
public string Source { get; set; }
// etc.
}
And action:
public IHttpActionResult Post(PostModel model)
{
//Omited
return Ok();
}

Injecting Non-User-Submitted Data For Use During Validation

From what I can tell, ASP.Net Core performs model state validation before calling the relevant controller action method. This means that code in the action method isn't given an opportunity to add data to the model before it is validated.
What is the ASP.Net Core way of giving a view model access to additional, non-user-submitted data prior to validation?
Example
What I'm trying to do (doesn't work).
The view model's Validate method expects data to be in ValidOptions. However, since validation occurs before the controller can set this property, validation causes the view model to throw an ArgumentNullException.
// From the Controller
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Process([Bind("SelectedId")]ViewModels.Import details)
{
// data needed for validation
details.ValidOptions = await service.ImportTypes.ToListAsync();
if (ModelState.ValidationState != ModelValidationState.Valid) {
// ...
}
}
// From ViewModels.Import
public IEnumerable<Option> ValidOptions { get; set; }
public int SelectdId {get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// throws ArgumentNullException because ValidOptions hasn't been set when this is executed
var option = ValidOptions.Single(t => t.Id == SelectdId);
//...
}
Probably many ways to skin a cat here. But the easiest for you is probably custom model binders. It's a way to "supplement" or change the binding of your model before it hits the controller. I will say that some see it as extremely bad practice to call an external service/repository at the point of model binding, but it does work and can come in handy.
You need to implement a class that inherits from IModelBinder.
public class MyViewModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
//Bind here. Including calling external services if you want.
}
}
Then you need to implement a provider, this essentially says "when" to bind.
public class MyViewModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(MyViewModel))
return new MyViewModelBinder();
return null;
}
}
In your configure method of your startup.cs, you need to add the provider to the ModelBinderProviders list.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc(config =>
config.ModelBinderProviders.Add(new MyViewModelBinderProvider())
);
}
Further Documentation :
http://dotnetcoretutorials.com/2016/12/28/custom-model-binders-asp-net-core/
http://intellitect.com/custom-model-binding-in-asp-net-core-1-0/
I don't think the official documentation has an article on custom model binders yet unfortunately.

ASP.Net Web API model binding not working like it does in MVC 3

I was under the impression that model binding in the ASP.Net Web API was supposed to support binding with the same minimum level of functionality supported by MVC.
Take the following controller:
public class WordsController : ApiController
{
private string[] _words = new [] { "apple", "ball", "cat", "dog" };
public IEnumerable<string> Get(SearchModel searchSearchModel)
{
return _words
.Where(w => w.Contains(searchSearchModel.Search))
.Take(searchSearchModel.Max);
}
}
public class SearchModel
{
public string Search { get; set; }
public int Max { get; set; }
}
I'm requesting it with:
http://localhost:62855/api/words?search=a&max=2
Unfortunately the model does not bind as it would in MVC. Why is this not binding as I would expect? I'm going to have a lot of different model types in my application. It would be nice if binding just worked, like it does in MVC.
Take a look at this: How WebAPI does Parameter Binding
You need to decorate your complex parameter like so:
public IEnumerable<string> Get([FromUri] SearchModel searchSearchModel)
OR
public IEnumerable<string> Get([ModelBinder] SearchModel searchSearchModel)
I have found the entire Web API 2 to be a difficult learning curve with lots of "Gotchas" I have read a few of the key books that cover many arcane nuances of this rich product offering. But basically, I thought there must be some core functionality that could take advantage of the best of the features. So, I set out to do four straight forward tasks.
1. Accept a query string, from a browser, into an Api2 Client and populate a simple .NET model.
2. Have the Client submit an async Post to an Api2 Server encoded in JSON extracted from the prior Model
3. Have the Server do a trivial conversion on the Post Request from the Client.
4. Pass it all back up to the Browser. This is it.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace Combined.Controllers // This is an ASP.NET Web Api 2 Story
{
// Paste the following string in your browser -- the goal is to convert the last name to lower case
// The return the result to the browser--You cant click on this one. This is all Model based. No Primitives.
// It is on the Local IIS--not IIS Express. This can be set in Project->Properties=>Web http://localhost/Combined with a "Create Virtual Directory"
// http://localhost/Combined/api/Combined?FirstName=JIM&LastName=LENNANE // Paste this in your browser After the Default Page it displayed
//
public class CombinedController : ApiController
{
// GET: api/Combined This handels a simple Query String request from a Browser
// What is important here is that populating the model is from the URI values NOT the body which is hidden
public Task<HttpResponseMessage> Get([FromUri]FromBrowserModel fromBrowser)
{
//
// The Client looks at the query string pairs from the Browser
// Then gets them ready to send to the server
//
RequestToServerModel requestToServerModel = new RequestToServerModel();
requestToServerModel.FirstName = fromBrowser.FirstName;
requestToServerModel.LastName = fromBrowser.LastName;
// Now the Client send the Request to the Server async and everyone awaits the Response
Task<HttpResponseMessage> response = PostAsyncToApi2Server("http://localhost/Combined/api/Combined", requestToServerModel );
return response; // The response from the Server should be sent back to the Browser from here.
}
async Task<HttpResponseMessage> PostAsyncToApi2Server(string uri, RequestToServerModel requestToServerModel)
{
using (var client = new HttpClient())
{
// Here the Method waits for the Request to the Server to complete
return await client.PostAsJsonAsync(uri, requestToServerModel)
.ContinueWith((postTask) => postTask.Result.EnsureSuccessStatusCode());
}
}
// POST: api/Combined This Handles the Inbound Post Request from the Client
// NOTICE THE [FromBody] Annotation. This is the key to extraction the model from the Body of the Post Request-- not the Uri ae in [FromUri]
// Also notice that there are no Async methods here. Not required, async would probably work also.
//
public HttpResponseMessage Post([FromBody]RequestToServerModel fromClient)
{
//
// Respond to an HttpClient request Synchronously
// The model is serialised into Json by specifying the Formatter Configuration.Formatters.JsonFormatter
// Prep the outbound response
ResponseToClientModel responseToClient = new ResponseToClientModel();
//
// The conversion to lower case is done here using the Request Body Data Model
//
responseToClient.FirstName = fromClient.FirstName.ToLower();
responseToClient.LastName = fromClient.LastName.ToLower();
//
// The Client should be waiting patiently for this result
//
using (HttpResponseMessage response = new HttpResponseMessage())
{
return this.Request.CreateResponse(HttpStatusCode.Created, responseToClient, Configuration.Formatters.JsonFormatter); // Respond only with the Status and the Model
}
}
public class FromBrowserModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class RequestToServerModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class ResponseToClientModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
}

Force a ASP.NET MVC 3 action parameter to use value from the URL, not object

Consider a model class
public class MyModel
{
public string Id { get; set; }
/* some other properties */
}
And a controller
public class MyController
{
[HttpPut]
public ActionResult Update(string id, MyModel model)
{
/* process */
}
}
The routing is registered as follows:
protected override void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute("MyController",
"api/my/{id}",
new { action = "Update", controller = "My"},
new { httpMethod = new HttpMethodConstraint(new[] { "PUT" }) });
}
When using a REST client and sending MyModel serialized as a JSON or XML request to this controller, a null "Id" property of "MyModel", overrides the "id" parameter of the action method, even if you post it to http://api.example.com/api/my/10.
How does one force ASP.NET MVC 3 to populate the "id" property from the URL (in this case "10") and ignore the "Id" property of the "MyModel"?
Note that I'm not using ASP.NET Web API.
Try using attribute [FromUri]. It's in "System.Web.Http". This attribute on action param id indicates it should be bonded using the url request.
using System.Web.Http;//at the top
public class MyController
{
[HttpPut]
public ActionResult Update([FromUri]string id, MyModel model)
{
/* process */
}
}
For MVC3 try to include web-api package(from nuget or manually) to use [FromUri] attribute. IF that is not possible then the only way I can think of getting it is from this.HttpContext.Request.QueryString["id"]
Instead of having id as a action method paramter declare it in action body. May have to change the url query api/my?id=1212. First try using api/my/{id} format.
var id = this.HttpContext.Request.QueryString["id"];

MVC3 load common data for views

I am developing an MVC3 "movie list" application containing several "sites" depending on the request hostname.
I am trying to use a strongly typed ViewModel like this (examples are simplified to get to the essence of the question):
class ViewModelBase
{
public int siteId { get; private set; }
public ViewModelBase(DbContext db)
{
siteId = <here I want to make a db-lookup based on the request hostname> <== This is my problem
}
}
class MoviesIndexViewModel : ViewModelBase
{
public List<Movie> movies { get; private set; }
public MoviesIndexViewModel(DbContext db) : base(db)
{
movies = db.Movies.where(m => m.SiteId == siteId).ToList();
}
}
An my controller would then just do this:
public class MoviesController : Controller
{
public ActionResult Index()
{
var model = new MoviesIndexViewModel(new MySpecialDbContext());
return View(model);
}
}
Question is: How will I get the "request host header" into the code line shown above? I know how to make the actual DB-lookup, but can I just access any request parameters here? Or should I supply something through parameters to the constructor?
I would not use Dbcontext in my view models. Read about Separation of concerns
Instead, use OnResultExecuting in your BaseController to add the common data:
protected override void OnResultExecuting(ResultExecutingContext filterContext)
{
var baseModel = filterContext.Controller.ViewData.Model as YourCustomModel;
if (baseModel != null)
{
// call a repository or whatever to add information to the model.
}
base.OnResultExecuting(filterContext);
}
Update
yes. The controller is the glue between the "model" (repositores, webservices or any other data source) and the view. The ViewModel is just an abstraction to move away logic from the view.
Here is the three main reasons you should use a view model:
http://blog.gauffin.org/2011/07/three-reasons-to-why-you-should-use-view-models/
And an alternative approach to handle common view data: http://blog.gauffin.org/2011/09/getting-information-into-the-layout-without-using-viewbag/

Resources