I'm just starting to use the TestHelpers in MvcContrib. I want to try and test an action method on my controller that itself tests if IsAjaxRequest() is true.
I've used the same code that is shown in the TestHelper samples to set up the TestControllerBuilder
_controller = new StarsController();
_builder = new TestControllerBuilder();
_builder.InitializeController(_controller);
So that _controller has all the faked/mocked HttpContext inside it, which is really great. But what do I do now to force IsAjaxRequest() on the internally faked Request object to return true?
Here's the code I used, the code in my question at the top of the page uses MvcContrib testhelpers to create a nicely faked controller (_controller) that internally has faked versions of HttpRequest, HttpResponse etc. Then as per Patrick's advice I created a new headers collection containing an entry for X-Requested-With. Then told _controller.HttpContext.Request.headers to return my headers collection whenever it attempts to look at headers (i.e. which is what occurs when IsAjaxRequest() is called).
var headers = new NameValueCollection();
headers.Add("X-Requested-With", "XMLHttpRequest");
_controller.HttpContext.Request.Stub(r => r.Headers).Return(headers);
Works like a treat.
You need to stub out the HttpRequest.Headers property to return a NameValueCollection that contains an entry for "X-Requested-With" with the value "XMLHttpRequest".
Related
How do you properly create a Web API POST of complex object or multiple parameters using Angular2?
I have a service component in Angular2 as seen below:
public signin(inputEmail: string, inputPassword: string): Observable<Response> {
return this.http.post('/api/account/signin', JSON.stringify({ Email: inputEmail, Password: inputPassword}), this.options);
}
The targeted web api is seen below:
[HttpPost]
[Route("signin")]
public async Task<IActionResult> Signin(string email, string password)
{
....
}
This does not work because I need to convert the parameters of the web api into a single POCO class entity with Email and Password properties and put the [FromBody] attribute: Signin([FromBody] Credential credential)
Without using [FromURI] (POST requests with query strings?), how can I make POSTs of multiple parameters or complex objects without converting these parameters into a single POCO class?
Because what if I have numerous Web API POST actions with parameters like (string sensitiveInfo1, string name, int sensitiveInfo2) or (ClassifiedInfo info, string sensitiveInfo1, string sensitiveInfo2), do I need to convert them all to POCO classes and always use [FromBody]?
PS.
I was using RestangularJS before and it can posts anything (mulitple primitive objects and complex objects) without my Web API actions having [FromBody] attributes. Will about to investigate how RestangularJS do it.
Without using [FromURI] (POST requests with query strings?), how can I make POSTs of multiple parameters or complex objects without converting these parameters into a single POCO class?
I know its not what you want to hear but out of the box this is not possible. It is not a limitation of the browser code that is making the request. This means it does not matter if you are using Angular, JQuery, straight JavaScript, or even RestangularJS. This is a limitation (I use that word loosely as I am sure this is by design) of Web API (any version). Here is the documentation on this design: Parameter Binding in ASP.NET Web API by Mike Wasson.
At most one parameter is allowed to read from the message body. So this will not work:
// Caution: Will not work!
public HttpResponseMessage Post([FromBody] int id, [FromBody] string name) { ... }
So the question becomes, what are your options?
Create a model
This is the thing you were trying to avoid but I list it first because this is how Web API was intended to behave. I have not yet heard a compelling reason not to do this. This approach allows you to extend your model easily without having to change the method signature. It also allows for model validation on the model itself. Personally I really like this approach.
public class SignInModel{
public string Email {get;set;}
public string Password {get;set;}
}
[HttpPost]
[Route("signin")]
public async Task<IActionResult> Signin(SignInModel signInModel)
{
// ....
}
I did not repeat your existing JavaScript code because what you have works as is with the above web api code
URL
Again, what you were trying to avoid. This does make what you want possible with the limitation that you have to pass these parameters using the Query string on the URL. The JavaScript would change but the signature you had on the Web API method would not.
public signin(inputEmail: string, inputPassword: string): Observable<Response> {
return this.http.post('/api/account/signin/?email=inputEmail&password=inputPassword', null, this.options);
}
I did not repeat your existing Web API code because what you have works as is with the above web JavaScript code (by default FromUri is assumed I believe)
Custom Model Binder
See Passing multiple POST parameters to Web API Controller Methods by Rick Strahl. This option allows you to create a custom model binder that could do what you are asking. It is a whole bunch of extra code though for, IMHO, not much benefit. Maybe there are situations where it would be useful although I really cannot think of any off the top of my head.
Dynamic
Finally you could also pass in a dynamic object as the parameter of your Web API. This is essentially the same as receiving the JSON as a string and making your Controller code responsible for the deserialization of content. Again, I believe that this would make your code worse in most situations as you have to implement custom validation and type checks. This answer was proposed previously on SO by Bes Ley. Again, maybe there are situations where it would be useful although I really cannot think of any off the top of my head.
If you call Web API 2.2 post method from Angular 2 type script, dont forget to add following header content and parameter object.
let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' });
var params = new URLSearchParams();
params.set('userid', '102');
params.set('username', 'foo');
return this._http.post('http://localhost:6579/api/PostUser', params.toString(), { headers: headers }).map(res => res.json());
Perhaps you should post with options:
{
headers: new Headers({
'Content-Type': 'application/x-www-form-urlencoded'
})
}
and encode data like
jQuery.param({user:'bla', password: 'bla'});
WebAPI does not provide this out of the box. If you try to get understanding of web API bindings, you might be able to figure out why.
I think this article might help.
The generic rules are:
– simple, string-convertible parameters (value types, strings, Guids, DateTimes and so on) are by default read from URI
– complex types are by default read from the body
– collections of simple parameters are by default read from the body too
– you cannot compose a single model based on input from both URI and request body, it has to be one or the other
I have fixed the issue of Angular2 HTTP Post ASP.NET MVC Web API
let headers = new Headers();
headers.append('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8');
let params: URLSearchParams = new URLSearchParams();
params.set('value', '2');
let options = new RequestOptions({
headers: headers//,
//search: params
});
let content = new URLSearchParams();
content.set('StudentName', 'Inderjit Singh';
content.set('Mobile', '+919041165398');
content.set('Nationality', 'Indian');
content.set('AdmissionNo', '6');
content.set('SectionCode', '1');
content.set('Gender', 'Male');
content.set('RegNo', '18585');
content.set('ClassCode', '1');
this.http.post('YOUR_URL', content.toString(), { headers: headers }).map((res: Response) => { console.log("data is==>" + res.text()); }).subscribe();
WebApi will be able to deserialize your Credential object provided the JSON object has the same field names (I am not sure about case so you may be right here). You seem to be missing the headers from the post call in your Angular2 component.
Can you check the Content-Type using Chrome Debugger or Fiddler? It should be application/json.
Try this, passing a complex class object into a single data parameter.
var SearchQuery = function () {
this.Alphabet = null;
this.Search = false;
this.Keyword = null;
this.RegionList = null;
};
var para = new SearchQuery();
{ data: JSON.stringify(para) } - Post Data
you can receive it using a JObject in your API controller and deserialize it as according to your classes.
I'm new to Spring MVC and trying to get a Post/Redirect/Get pattern working. We're trying to implement a survey where each page can display a variable number of questions. The way I'd like to implement this is a GET handler that prepares the next survey page and then hands that off to the view. In the same Controller, have a Post handler that processes the form's answers to the survey questions, submits that to the survey service, which returns the next page of questions, and then redirects that next surveyPage to the getNextPage GET handler.
Most of it is working, except the problem is that I don't know how to hand that 'next survey page' object from the POST handler to the getNextPage GET handler in the redirect. The redirect is working; it goes from the POST method to the GET method, but the surveyPage ModelAttribute is a new object in the GET method, and not the one that was set at the end of the POST method. As you can see, I've tried using ModelAttribute, but it doesn't work. I also tried using #SessionAttributes above the class, but then got a HttpSessionRequiredException.
We didn't know how to handle the dynamic form containing a variable # of questions with Spring MVC Forms, so we just did straight JSTL. It's funky but it works. That funkiness is what resulted in using the #RequestBody and SurveyPageBean coming back with the Post. Honestly, I don't know how the SurveyPageBean is populated. It looks like some Spring MVC magic, but it's working so I'm leaving it alone for now (another developer did this and then I picked it up, and we're both new to Spring MVC). Please don't get distracted by the unusual form handling, unless that is part of the problem with the empty surveyPage ModelAttribute not being redirected.
Here's the Controller snippet:
#Controller
#RequestMapping("/surveyPage")
public class SurveyPageController{
#RequestMapping(method=RequestMethod.GET)
public String getNextPage(#ModelAttribute("surveyPage") SurveyPage surveyPage, Model model) {
if(surveyPage.getPageId() == null) {
// call to surveyService (defined elsewhere) to start Survey and get first page
surveyPage = surveyService.startSurvey("New Survey");
}
model.addAttribute("surveyPage", surveyPage);
return "surveyPage";
}
#RequestMapping(method=RequestMethod.POST)
public String processSubmit(#RequestBody String body, SurveyPageBean submitQuestionBean, Model model, #ModelAttribute("surveyPage") SurveyPage surveyPage) {
// process form results, package them up and send to service, which
// returns the next page, if any
surveyPage = surveyService.submitPage(SurveyPageWithAnswers);
if (results.getPageId() == null) {
// The survey is done
surveyPage = surveyService.quitSurvey(surveyId);
return "redirect:done";
}
model.addAttribute("surveyPage ", surveyPage );
return "redirect:surveyPage";
}
Use Flash Attributes as shown in Warlock's Thoughts.
#RequestMapping(method = RequestMethod.POST)
public String handleFormSubmission(..., final RedirectAttributes redirectAttrs) {
...
redirectAttrs.addFlashAttribute("AttributeName", value);
return "redirect:to_some_url_handled_by_BController";
}
Your GET takes the surveyPage as a model attribute, which means it is reading it from the URL. In the POST, rather than adding the surveyPage to the model (which is lost because you are telling the client to redirect, which creates a new request and therefore a new model) you should add the surveyPage as a query parameter in your "redirect:surveyPage" You'll have to look at how the surveyPage is constructed from the query params in order to know what to put on the query string.
If, for instance, the surveyPage is constructed from a user, page number, and question count or something, I believe you could do something like "redirect:surveyPage?userId=1234&pageNumber=5678&questionCount=12 in order to pass that model attribute along.
The populated catList is always Count=0 when the code jumps to CreateProduct() so I take it it does not get delivered.
Considering RouteValueDictionary does not do this ? Any other way?
public ActionResult GetCats(int CatID)
{
List<Category> catList = new List<Category>();
if (CatID >= 0 )
{
catList = context.Categories.Where(x => x.PCATID == CatID).ToList();
}
return RedirectToAction("CreateProduct", "Admin", new { catList });
}
public ActionResult CreateProduct(List<Category> catList) { }
You are actually trying to use controllers to do data access.
Move the "GetCats" data retrieval into your business layer (Service object, Repository, whatever suits you).
Then, CreateProduct will need to be there twice (2 signatures). One with no parameters in which you are going to call "GetCats" from your business layer and send it to the view.
The other implementation is going to be the flagged with the HttpPostAttribute and will contain in parameters all the necessary information to create a cat.
That's all. Simple and easy.
You could place any items that you need in TempData then call RedirectToAction.
RedirectToAction simply returns a "302" code to the browser with the URL to redirect to. When this happens your browser performs a GET with that URL.
Your RouteValues need to be simple. You can't really pass complex object or collections using a route value.
if you don't care about the browser url changing you could just
return CreateProduct(catList)
I am trying to write a module/areas based mvc3 CMS using razor view engine. I have two layout views, _site.cshtml and _modules.cshtml. The _site.cshtml has an #RenderBody() section. My application always calls a view called "index.cshtml" which has its layout set to _site.cshtml page.
The Problem: The problem is that my modules/areas are rendered before doctype element - and not inside the RenderBody section of layout page.
It looks like what you're trying to do is the equivalent of Html.RenderAction (or Html.Action), so why are you writing all that code yourself instead of using the built-in functionality?
In short, its your Invoke() method! The razor engine works by writing to an in memory stream, and then parses it to the response. The reason you are getting that behaviour is because your Invoke() method is writing straight to the response stream; i.e. before the in memory stream is parsed.
I came across similar behaviour when using the Html.RenderAction(), which pointed to an action that returned a PartialView. The workaround was to use Html.Action(). The difference being is that Action() returns a string which gets appended to the in memory stream, and RenderAction() writes to the directly to the response.
If you post the code for your Invoke() method, I may be able to help you futher!
| -- Edit -- |
OK, this turned out to be more complex that initially anticipated. The problem is that I could not get ProcessRequest() to append to the current response; however, I may have a solution.
public string ProcessRoute(ViewContext viewContext, RouteData routeData)
{
var urlHelper = new UrlHelper(viewContext.RequestContext);
var currentUrl = urlHelper.Action(routeData.Values["action"].ToString(),
routeData.Values["controller"].ToString(), routeData.DataTokens);
var stringWriter = new StringWriter();
var simpleWorkerRequest = new SimpleWorkerRequest(currentUrl, "", stringWriter);
var context = new HttpContext(simpleWorkerRequest);
var contextBase = new HttpContextWrapper(context);
var requestContext = new RequestContext(contextBase, routeData);
var httpHandler = routeData.RouteHandler.GetHttpHandler(requestContext);
httpHandler.ProcessRequest(context);
context.Response.End();
stringWriter.Flush();
return stringWriter.GetStringBuilder().ToString();
}
The code above generates a new Request and returns the Request's HTML as a string. By doing this, the result is appended as part of the current response. You can now rewrite your Invoke() function to return a string, which can be displayed on your View.
public string Invoke(ViewContext viewContext)
{
if (_mvcHandler == null)
{
var routeData = new RouteData(context.RouteData.Route,
context.RouteData.RouteHandler);
routeData.Values.Add("id", _id);
routeData.Values.Add("moduleName", _moduleName);
routeData.Values.Add("controller", _controllerName);
routeData.Values.Add("action", _actionName);
routeData.Values.Add("pageContext", _pageContext);
if (!string.IsNullOrEmpty(_preferredNamespace))
{
routeData.DataTokens.Add("Namespaces", new[] { _preferredNamespace });
}
return ProcessRoute(viewContext, routeData);
}
return string.Empty;
}
You may also have to change;
mr.Invoke(ViewContext);
To;
Html.Raw(mr.Invoke(ViewContext));
In order stop the HTML encoding behaviour.
| -- Note -- |
Since I don't have your ModuleRequests class, I couldn't test this code specifically for your scenario. Instead, I replicated the problem as best as I could and solved it.
Please let me know if you have any questions.
Matt
Let me post a plausible second alternative to your methods while I try and solve the problem above.
There is a html extension in MVC called Html.Partial(), which allows you to use routedata to return the result of a controller action.
So, if you had an AccountController with an Register method that returned a PartialView result, then it would be appended to the current page.
public class AccountController() : Controller
{
public ActionResult Register()
{
return PartialView();
}
}
The call to this action could look something like;
#Html.Partial("Account", "Register");
| -- Note -- |
Don't use #Html.RenderPartial(), or you will have the same problem as above!
I am currently posting away from a computer, so I can't test this theory!
Hope this helps!
Matt
In my particular situation, I have a couple of solutions to my problem. I want to find out which one is more feasible. In this case, I can also achieve my goal by returning a JSON object from my server side code; however, I do not know how it is done and what the best way of doing it is.
First off, I don't need a full aspx page as I only need a response returned from code. So, do I use web services, a handler, or is there any other specific way to do this?
Is this solution feasible? Do I build the JSON string using the StringBuilder class and inject that string into the target aspx page? Are there any precautions or things that I should be aware of?
I appreciate your ideas.
Regards,
Kemal
------------UPDATE !------------
Suppose I have a JSON object in my userlist.aspx page, which then I use with jQuery...
{"menu": {
"id": "color1",
"value": "color",
"popup": {
"menuitem": [
{"value": "Red"},
{"value": "Green"},
{"value": "Yellow"}
]
}
}} // example taken from the json.org/example page
Now when I want to add a new menu items from my aspx page, what do I do... I think this way my question is more specific...
Lets assume I create a new string in my aspx code, as such "{"value": "Blue"}. How do I inject this into the already existing itemlist in the target page? Or is this not the correct approach to this kind of situation? If not, how else can it be achieved?
Also, if I wanted to fire a jQuery event when a new item is added to this list, how is this achieved?
------------UPDATE 2 on 26 August 2015 ------------
By the time I asked this question, the way I was approaching the problem was in another aspect. I am now more proficient in the subject and can gladly accept the most voted answer since the approach to this question clearly should not include the already existing JSON and output a new one from the code as #DavGarcia also suggests.
In your Page_Load you will want to clear out the normal output and write your own, for example:
string json = "{\"name\":\"Joe\"}";
Response.Clear();
Response.ContentType = "application/json; charset=utf-8";
Response.Write(json);
Response.End();
To convert a C# object to JSON you can use a library such as Json.NET.
Instead of getting your .aspx page to output JSON though, consider using a Web Service (asmx) or WCF, both of which can output JSON.
no problem doing it with asp.... it's most natural to do so with MVC, but can be done with standard asp as well.
The MVC framework has all sorts of helper classes for JSON, if you can, I'd suggest sussing in some MVC-love, if not, you can probably easily just get the JSON helper classes used by MVC in and use them in the context of asp.net.
edit:
here's an example of how to return JSON data with MVC. This would be in your controller class. This is out of the box functionality with MVC--when you crate a new MVC project this stuff gets auto-created so it's nothing special. The only thing that I"m doing is returning an actionResult that is JSON. The JSON method I'm calling is a method on the Controller class. This is all very basic, default MVC stuff:
public ActionResult GetData()
{
var data = new { Name="kevin", Age=40 };
return Json(data, JsonRequestBehavior.AllowGet);
}
This return data could be called via JQuery as an ajax call thusly:
$.get("/Reader/GetData/", function(data) { someJavacriptMethodOnData(data); });
With ASP.NET Web Pages you can do this on a single page as a basic GET example (the simplest possible thing that can work.
var json = Json.Encode(new {
orientation = Cache["orientation"],
alerted = Cache["alerted"] as bool?,
since = Cache["since"] as DateTime?
});
Response.Write(json);
If you get code behind, use some like this
MyCustomObject myObject = new MyCustomObject();
myObject.name='try';
//OBJECT -> JSON
var javaScriptSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
string myObjectJson = javaScriptSerializer.Serialize(myObject);
//return JSON
Response.Clear();
Response.ContentType = "application/json; charset=utf-8";
Response.Write(myObjectJson );
Response.End();
So you return a json object serialized with all attributes of MyCustomObject.