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

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"];

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();
}

webapi actionfilters, how to inject a value when using different argument types that inherit from a base type

I have a base request type..
class RequestBase
{
public string inputId;
public string derivedid;
}
and types that inherit ..
class RequestA : RequestBase
{
public string name;
}
and
class RequestB : RequestBase
{
public string color;
}
I have a webapi service, some actions take an input parameter of RequestA, some take RequestB
[HttpPost]
[MyFilter]
[ActionName("Process1")]
public HttpResponseMessage Process1(RequestA request)
{
//do something with request.derivedId
}
[HttpPost]
[MyFilter]
[ActionName("Process2")]
public HttpResponseMessage Process2(RequestB request)
{
//do something with request.derivedId
}
I have an actionfilter that takes the inputId from the request and generates a derivedId
public override void OnActionExecuting(HttpActionContext actionContext)
{
RequestBase request = (RequestBase)actionContext.ActionArguments["request"];
string inputId = request.inputId;
string derivedId = inputId + "123";
// ?? somehow inject derivedId back into the actionContext so that my controller methods can access?
}
As my comment states above, I'd like to populate the derivedId field and have it accessible to my controller methods.
Thanks in advance
There's a few solutions to this problem already described in this thread - one of them should suit you:
ASP.NET MVC Pass object from Custom Action Filter to Action

MVC3 REST service - how do I access the request body content for a PUT or POST request?

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();

asp.net mvc 3 pass parameters

I have controller which gets list of keys and i want to pass them to another controller action:
public class DocumentController : Controller
{
private List<DocumentKey> _keys = new List<DocumentKey>();
[HttpPost]
public ActionResult Send(Document doc, IEnumerable<HttpPostedFileBase> file)
{
...
_keys = getKeys();
return RedirectToAction("Status", "Home", _keys);
}
i try to use
return RedirectToAction("Status", "Home", _keys);
and
return RedirectToAction("Status", "Home", new {keys = _keys});
But in Status keys is always null or has count = 0.
public class HomeController : Controller
{
public ActionResult Status(List<DocumentKey> keys)
{
return View(keys);
}
I can pass simple data, for example:
return RedirectToAction("Status", "Home", new {key = _keys.First().ToString()});
public ActionResult Status(string key)
this works, but is there a way to pass collection ?
Passing this type of data as query string parameters will not work as you are attempting to do so. If you need data to persist until your next request, it may be feasible to push the "keys" into tempdata.
public class DocumentController : Controller
{
private List<DocumentKey> _keys = new List<DocumentKey>();
[HttpPost]
public ActionResult Send(Document doc, IEnumerable<HttpPostedFileBase> file)
{
...
TempData["_keys"] = getKeys();
return RedirectToAction("Status", "Home");
}
public class HomeController : Controller
{
public ActionResult Status()
{
List<DocumentKey> keys = TempData["_keys"];
return View(keys);
}
RedirectToAction does a round trip by sending a HTTP 302 to the client. Is that really what you need?
Because if you don't, then the easiest would be to call your HomeController's Status method directly. You would also need to replace your call to View to explicitly specify the name of the view, i.e:
public ActionResult Status(List<DocumentKey> keys)
{
return View("Status", keys);
}
If you do need the round trip, then a solution would be to use TempData to store your data as suggested by Jesse.

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