I am using MVC preview 2 framework to develop web sites and I am following MVCStorefront tutorials to get a good feel on MVC.
Can you tell me why I can't use RenderView() method ?
Am I missing something or can I use View() instead ?
What's the difference between these methods..
Thanks
Here is where Rob is using RenderView in his tutorial.
[TestMethod]
public void CatalogController_IndexMethod_ShouldReturn_Categories_And_Data_For_Parent1() {
CatalogController c = new CatalogController(_repository);
RenderViewResult result = (RenderViewResult)c.Index("Parent1", "Sub10");
CatalogController.CatalogData data = (CatalogController.CatalogData)result.ViewData;
Assert.IsNotNull(data.Category);
Assert.IsNotNull(data.SubCategory);
Assert.IsNotNull(data.SubCategory.Products);
Assert.IsTrue(data.SubCategory.Products.Count() > 0);
Assert.IsNotNull(result);
}
I can't use RenderView. It says " the name 'RenderView' does not exist in the current context
Here's the link :
http://www.asp.net/learn/mvc-videos/video-357.aspx
Here is an index method from the CatalogController class :
public ActionResult Index(string category, string subcategory) {
//instantiate the service
CatalogService svc = new CatalogService(_repository);
//the ViewData class
CatalogData data = new CatalogData();
//pull all the categories for the navigation
data.Categories = svc.GetCategories();
//pull the category based on subcategory name
data.Category = data.Categories.WithCategoryName(category);
//catch for bad data
if (data.Category == null) {
data.Category = data.Categories.DefaultCategory();
data.SubCategory = data.Category.SubCategories[0];
} else {
data.SubCategory = data.Categories.WithCategoryName(subcategory);
//catch for bad SubCategory
data.SubCategory= data.SubCategory ?? data.Category.SubCategories[0];
}
return RenderView("Index",data);
}
I am also having a problem with the casting of result.ViewData in CatalogData type which is class that contains data. It says : Cannot convert type System.Web.Mvc.ViewDataDictionary to Commerce.MVC.Web.Controllers.CatalogController.CatalogData
The video you are watching is unfortunately outdated - it is from ASP.NET MVC 1.0 Preview 2. Since then ASP.NET MVC 1.0 RTM has shipped and there are previews of ASP.NET MVC 2 available.
In ASP.NET MVC 1.0 Preview 2 and earlier action methods returned 'void' so they had to explicitly perform a result, such as render a view:
public void Index() {
// do some work...
RenderView("Index");
}
In ASP.NET MVC 1.0 Preview 3 (Refresh?) and newer, action methods return a result object, which then actually performs the result:
public ActionResult Index() {
// do some work...
return View("Index");
// or you could also just say "return View();" and MVC figures out the view name
}
The main reason this changed is that it allows for much better unit testing. Action methods now only perform the "application logic" and don't worry about exactly how to render a view. The unit test can simply inspect the results of the application logic and then verify that the next desired step was "render a view."
A lot of type names and method names have changed as well to make them shorter and simpler to use. For example, RenderView is just View and RenderViewResult is just RenderView.
My apologize if I resurrect a dead topic like this, but I was having the same issues as OP and I found my solution. Therefore I would reply here in case anybody who is following Rob's StoreFront series would find the solution also.
[TestMethod]
public void CatalogController_IndexMethod_ShouldReturn_Categories_And_Data_For_Parent1() {
CatalogController c = new CatalogController(_repository);
ViewResult result = c.Index("Parent1", "Sub10") as ViewResult;
CatalogController.CatalogData data = result.ViewData.Model as CatalogController.CatalogData;
Assert.IsNotNull(data.Category);
Assert.IsNotNull(data.SubCategory);
Assert.IsNotNull(data.SubCategory.Products);
Assert.IsTrue(data.SubCategory.Products.Count() > 0);
Assert.AreEqual("Parent1", data.Category.Name);
Assert.AreEqual("Sub10", data.SubCategory.Name);
Assert.IsNotNull(result);
}
Related
I've got what I take to be a very standard controller:
Here's the controller definition (a bit of dependency injection, but standard):
public class SeriesController : Controller
{
private readonly IHostingEnvironment _env;
public SeriesController(IHostingEnvironment env)
{
_env = env;
}
[HttpGet("/series/{id:int?}/{title?}")]
public IActionResult Index(int id, string title)
{
if (id > 0)
{
var populateSeriesItem = new PopulateSeriesItem(_env, new SqlConnection());
var seriesItem = populateSeriesItem.GenerateSeriesItem(id);
//...
If the id is absent (or 0), then it displays all records; if it's 1 or greater, it displays a single record. (This is how the client wants it!)
I call it as follows:
https://localhost/series/3/title
or
https://localhost/series/4/
but the problem is id is always 0 (and title null).
That's the case if I type it into the URL bar or specify id manually (i.e., /series?id=3)
I just can't figure out what I'm missing.
The exact same setup works perfectly with a different controller.
[HttpGet("/books/{id:int?}/{title?}")]
public ActionResult Index(int id, string title)
{
if (id > 0)
{
var populateBookItem = new PopulateBookItem(new SqlConnection(), _env);
var bookItem = populateBookItem.GenerateBookItem(id);
That one works.
This is the routing configuration (just standard):
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
I must be missing something obvious, but I'm baffled!
Any advice greatly appreciated.
Update:
I've stripped away all the functionality from the controller. I'm just literally passing it to a view.
This works: https://localhost:44319/series/14/megacities - the view reports id = 14.
This works: https://localhost:44319/series/5/megacities - the view reports id = 5
This works: https://localhost:44319/series/14/business-with-china - the view reports id = 14.
This doesn't work: https://localhost:44319/series/5/business-with-china - it goes to https://localhost:44319/series/0.
There are other URLs that follow the same pattern. Some working, some not working.
If I strip out the title parameter, they all seem to work.
I've no idea why!
Remove routes.MapRoute, it isn't necessary for Web API.
HTTPGet should look like [HttpGet("/books/{id:int?}")]
Add query parameters to request GET api/books/5?title=abc
Method definition:
[HttpGet("/books/{id:int?}")]
public ActionResult Index(int id, [FromQuery] string title)
{
//code here
}
If there is a mismatch in names, parameter will resolve as default value - 0 in case of numbers and null otherwise
In the example below, url has param1 whereas the method has parameter name as param2.
[HttpGet("/foo/{param1}")]
public ActionResult Foo(int param2)
{
//code here
}
I had the same problem so I found this link. I accidentally found the solution for my problem so I even it is not related to your, I post it for anyone who comes here who may have the same problem as me.
I am using C# and .net Core 2.2
I was getting id value to be 0 no matter what I did.
It happened that the parameter name I chose, which was "page", the page number, needed to be "id" the same name as the specified in {id?}
Thanks for everybody's help.
I've solved the problem, though I don't know what was causing it. (I think it might have been a browser caching issue.)
Anyway, I restarted the server, and everything now works as it should.
Let me premise this by saying I am transitioning from Classic ASP to .net so my .net knowledge is all through books, not use. I am working on a shopping cart platform and have been tasked with creating 3 different display types (horizontal slider, vertical slider and grid) that will be able to be utilized by 2 different models.
I got the new views to work with my first model and thought I was in the clear and plugged in the second, but then I started getting an error about the models not being the same. I am trying to figure out what the best way to handle this. The solutions I have come up with are
Using A View Model - this seems like it is supposed to only be used when you need to combine 2 models into one and that is really the opposite of what I need, I need to make 1 view work for 2 models.
Make separate views for each type. This seems logical but is going to result in 5 new view files that are almost identical to those I have already created, it seemed redundant.
Use renderPartial and convert the models - not really sure how I would even accomplish this, but my though was to renderpartial out the creation of the actual displays (horizontal slider, vertical slider and grid view)
I am having a hard time determining what the best course of action is. I know that with MVC and .net you are supposed to never code the same thing twice and I think making the separate views is doing exactly that. Any thoughts on how to best approach this would be greatly appreciated.
This is the way I'm doing it right now on my project, I don't know if it's the best way or not but for me works fine.
Basically on my website's homepage I need to have to 2 different models to handle 2 forms posting to the same Method in the controller
namespace Website.Models.ViewModels
{
public class HomePageModels
{
public SearchFlyModel SearchFly { get; set; }
public CarRequestModel CarRequest { get; set; }
public int form { get; set; }
public HomePageModels()
{
SearchFly = new SearchFlyModel();
CarRequest = new CarRequestModel();
}
}
}
Then on the controller's method I need to know which form was posted to know which form I need to validate, because Asp.net automatically validate the model , it will ,by default validate both models in your ViewModel , and this affects the ModelState and when you check if your model is valid doing ModelState.isValid , this will return false because you only posted one form and not the other which is completely empty.
To manage that, I do the following
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(HomePageModels model)
{
ModelState.Clear();
if (model.form == 0)
{
try
{
ValidateModel(model.SearchFly);
return this.SearchFly(model.SearchFly);
}
catch (Exception e)
{
}
}
else
{
try
{
ValidateModel(model.CarRequest);
return this.SearchCar(model.CarRequest);
}
catch (Exception e)
{
}
}
var modelHomePage = new HomePageModels()
{
SearchFly = model.SearchFly,
CarRequest = model.CarRequest,
form=model.form
};
return View(modelHomePage);
}
Both forms are posting to the same action because I need to return the form's validations error so the user can fix it.
I have a webforms project, and am attempting to run some code that allows me to make a call to an MVC route and then render the result within the body of the web forms page.
There are a couple of HttpResponse/Request/Context wrappers which I use to execute a call to an MVC route, e.g.:
private static string RenderInternal(string path)
{
var responseWriter = new StringWriter();
var mvcResponse = new MvcPlayerHttpResponseWrapper(responseWriter, PageRenderer.CurrentPageId);
var mvcRequest = new MvcPlayerHttpRequestWrapper(Request, path);
var mvcContext = new MvcPlayerHttpContextWrapper(Context, mvcResponse, mvcRequest);
lock (HttpContext.Current)
{
new MvcHttpHandlerWrapper().PublicProcessRequest(mvcContext);
}
...
The code works fine for executing simple MVC routes, for e.g. "/Home/Index". But I can't specify any query string parameters (e.g. "/Home/Index?foo=bar") as they simply get ignored. I have tried to set the QueryString directly within the RequestWrapper instance, like so:
public class MvcPlayerHttpRequestWrapper : HttpRequestWrapper
{
private readonly string _path;
private readonly NameValueCollection query = new NameValueCollection();
public MvcPlayerHttpRequestWrapper(HttpRequest httpRequest, string path)
: base(httpRequest)
{
var parts = path.Split('?');
if (parts.Length > 1)
{
query = ExtractQueryString(parts[1]);
}
_path = parts[0];
}
public override string Path
{
get
{
return _path;
}
}
public override NameValueCollection QueryString
{
get
{
return query;
}
}
...
When debugging I can see the correct values are in the "request.QueryString", but the values never get bound to the method parameter.
Does anyone know how QueryString values are used and bound from an http request to an MVC controller action?
It seems like the handling of the QueryString value is more complex than I anticipated. I have a limited knowledge of the internals of the MVC Request pipeline.
I have been trying to research the internals myself and will continue to do so. If I find anything I will update this post appropriately.
I have also created a very simple web forms project containing only the code needed to produce this problem and have shared it via dropbox: https://www.dropbox.com/s/vi6erzw24813zq1/StackMvcGetQuestion.zip
The project simply contains one Default.aspx page, a Controller, and the MvcWrapper class used to render out the result of an MVC path. If you look at the Default.aspx.cs you will see a route path containing a querystring parameter is passed in, but it never binds against the parameter on the action.
As a quick reference, here are some extracts from that web project.
The controller:
public class HomeController : Controller
{
public ActionResult Index(string foo)
{
return Content(string.Format("<p>foo = {0}</p>", foo));
}
}
The Default.aspx page:
protected void Page_Load(object sender, EventArgs e)
{
string path = "/Home/Index?foo=baz";
divMvcOutput.InnerHtml = MvcWrapper.MvcPlayerFunctions.Render(path);
}
I have been struggling with this for quite a while now, so would appreciate any advice in any form. :)
MVC framework will try to fill the values of the parameters of the action method from the query string (and other available data such as posted form fields, etc.), that part you got right. The part you missed is that it does so by matching the name of the parameter with the value names passed in. So if you have a method MyMethod in Controller MyController with the signature:
public ActionResult MyMethod(string Path)
{
//Some code goes here
}
The query string (or one of the other sources of variables) must contain a variable named "Path" for the framework to be able to detect it. The query string should be /MyController/MyMethod?Path=Baz
Ok. This was a long debugging session :) and this will be a long response, so bear with me :)
First how MVC works. When you call an action method with input parameters, the framework will call a class called "DefaultModelBinder" that will try and provide a value for each basic type (int, long, etc.) and instance of complex types (objects). This model binder will depend on something called the ValueProvider collection to look for variable names in query string, submitted forms, etc. One of the ValueProviders that interests us the most is the QueryStringValueProvider. As you can guess, it gets the variables defined in the query string. Deep inside the framework, this class calls HttpContext.Current to retrieve the values of the query string instead of relying on the ones being passed to it. In your setup this is causing it to see the original request with localhost:xxxx/Default.aspx as the underlying request causing it to see an empty query string. In fact inside the Action method (Bar in your case) you can get the value this.QueryString["variable"] and it will have the right value.
I modified the Player.cs file to use a web client to make a call to an MVC application running in a separate copy of VS and it worked perfectly. So I suggest you run your mvc application separately and call into it and it should work fine.
I'm new to ASP.NET MVC and want to create a small order management tool. My database contains the tables Orders and Articles (and a few other ones), and I generated an EF Model from my database, so I can use the full power of the EF mappings (e.g. db.Orders.Articles)
My two main relations which I'm concerned about are Orders and Articles.
An order can have many articles
An article can only belong to one order.
I've created an OrdersController with an Create action to create an order:
//
// GET: /Orders/Create
public ActionResult Create()
{
Order order = new Order()
{
// filling some order columns, e.g. date
};
Article article = new Article()
{
// ... article columns
};
order.Articles.Add(article);
return View(order);
}
//
// POST: /Orders/Create
[HttpPost]
public ActionResult Create(Order order)
{
// i know i should care more about error handling, but now ommit it
db.Orders.AddObject(order);
db.SaveChanges();
return RedirectToAction("index");
}
So I'm directly binding an EF Object to a view (read somewhere not to do that and use a view model instead, but don't really know what that view model should look like)
My view contains the Order form as well as the article form (because i want to create a order and articles at the same time and not seperate). I used these greate EditorFor Methods to do that.
And now to my problem: If i hit the submit button, the app crashes as soon as it comes to the HttpPost Create Method (when mapping the order) with this error message:
Error Message: The EntityCollection
has already been initialized. The
InitializeRelatedCollection method
should only be called to initialize a
new EntityCollection during
deserialization of an object graph.
If i hit continue in VS2010, it will complete saving the order - so my question is how to solve this problem in a reliable way.
Thanks in advance and sorry for that long story :)
I solved my problem now by using a separate ViewModel like #Yakimych advised me. However I did not copy all the attributes from the EF models, instead I just refer to them. My ViewModel looks like this:
public class NewOrderViewModel {
public Order { get; set; }
public List<Article> { get; set; }
}
I finished NerdDinner tutorial and now I'm playing a bit with project.
Index page shows all upcoming dinners:
public ActionResult Index()
{
var dinners = dinnerRepository.FindUpComingDinners().ToList();
return View(dinners);
}
In DinnerRepository class I have method FindAllDinners and I would like to add to above Index method number of all dinners, something like this:
public ActionResult Index()
{
var dinners = dinnerRepository.FindUpComingDinners().ToList();
var numberOfAllDinners = dinnerRepository.FindAllDinners().Count();
return View(dinners, numberOfAllDinners);
}
Of course, this doesn't work. As I'm pretty new to OOP I would need help with this one.
Thanks,
Ile
Create view model:
public class DinnerViewModel
{
public List<Dinner> Dinners { get; set; }
public int NumberOfAllDinners { get; set; }
}
public ActionResult Index()
{
var dinners = dinnerRepository.FindUpComingDinners().ToList();
var numberOfAllDinners = dinnerRepository.FindAllDinners().Count();
return View(new DinnerViewModel { Dinners = dinners, NumberOfAllDinners = numberOfAllDinners } );
}
You need to create a "wrapper" object that contains the objects you wish to pass as public properties of it. For instance, create an object called DinnerViewModel and give it two properties and set these with two properties, one a List called Dinners and one an int called DinnerCount. Then pass the DinnerViewModel to the view and you can then access Model.Dinners and Model.DinnerCount
In your case I would prefer the solution mentioned by LukLed.
In general you could of course also transfer multiple values from your controller to your view using ViewData:
ViewData["dinners"] = dinners;
ViewData["numberOfAllDinners"] = 150;
...
For more information also take a look at this link.
Just simply use dinners.Count property instead.
Remember, you start off using the ViewData inherts in you .aspx filesand returning the same in you return statements. Because of that, I figure that it was an issue with the Inherits attribute on the top of the ASP.NET files. But, if you are getting the error when trying to create or edit a new Dinner when you are on the 'Upcoming Dinners' page (generated from the Details.aspx and the LINQ file that gets all Dinners that are after todays date), go into your 'Controllers' directory, specifically the DinnerController.cs. Then look at the Edit and or Create methods. the answer lies right here. If you put breakpoints on these methods, you should be able to figure it out. If not, continue reading:
Look where it fails, the 'return...' line. Maybe I am the only person who forgot to change this, but my error is the same as people are getting in this page and this os how I fixed it.....the 'return(dinner)' line, in Create and Edit (and any others that you are having issues with), they are using the NerDinner.Model.Dinner / ViewData method. However, if you change it to the ViewModel return method instead, it should fix it, For example: 'return(new DinnerFormViewModel(dinner));', it should work for you. I hope this helps, as it was what my issue was. Just a simple overlook.