I'm a web developer new to using the MVC3 framework. We're building a site that implements a lot of sub folders for different segments of our audience. This routing concept is throwing a wrench in our structure for SEO.
In my global.asax file under the routing section we have:
routes.MapRoute("test", "test/{testFirst}/{testSecond}",
new { controller = "test", action = "RouteTest", testSecond = UrlParameter.Optional });
and in my controller we have:
public ActionResult RouteTest(string testFirst, string testSecond)
{
return View(testFirst, testSecond);
}
When I run the site and try to go to /test/test/index it won't pull up the view. It's stuck looking for test.cshtml which doesn't exist because it's a folder not a file.
Any ideas on to how make nested folders work?
EDIT:
Here's a branch of the structure we want and maybe it will help with what I'm trying to accomplish.
This is kind of hard to show but it should get the idea across. We have 5 different audiences that come to the site. I broke down 1 audience and what the flow of that audience is.
Not all segments will have products some are just content other segments have that 3rd level and have products to view
audience
segment
segment
products
segment
products
segment
This is the basic structure that we want the URLs to take
domain.com/audience/segment/products/(productsname)
Suggestions on how to make this possible
You are using the wrong overload for the View() method. Here's what you're using when you call View(testFirst, testSecond):
protected internal ViewResult View(
string viewName,
string masterName
)
MSDN Reference.
By putting "test" for the viewName, you're telling the Controller to render a View called Test (test.cshtml). Which you don't have.
It sounds to me like you are trying to correlate WebForms with MVC. It is not the same, and you are seeing a prime example with routing. ASP.NET MVC doesn't work off of the NTFS structure (folders and files). It relies on routing through route definitions.
If you are looking to render the View "RouteTest", then do something like this:
public ActionResult RouteTest(string testFirst, string testSecond)
{
ViewBag.testFirst = testFirst;
ViewBag.testSecond = testSecond;
return View();
}
This will render the "RouteTest" view and in your dynamic object ViewBag you will have access to two properties: testFirst and testSecond. In your view you can pull those values. (Although I highly recommend strongly-typed Views using a ViewModel)
Example Solution
ViewModel
public class TestData
{
public string testFirst { get ; set ; }
public string testSecond { get ; set ; }
}
Controller
public ActionResult RouteTest(string testFirst, string testSecond)
{
TestData td = new TestData();
td.testFirst = testFirst;
td.testSecond = testSecond;
return View(td);
}
Strongly-Typed View
#model TestData
#Html.Label(Model.testFirst)
#Html.Label(Model.testSecond)
Related
ViewData and ViewBag allows you to access any data in view that was passed from controller.
The main difference between those two is the way you are accessing the data.
In ViewBag you are accessing data using string as keys - ViewBag[“numbers”]
In ViewData you are accessing data using properties - ViewData.numbers.
ViewData example
CONTROLLER
var Numbers = new List<int> { 1, 2, 3 };
ViewData["numbers"] = Numbers;
VIEW
<ul>
#foreach (var number in (List<int>)ViewData["numbers"])
{
<li>#number</li>
}
</ul>
ViewBag example
CONTROLLER
var Numbers = new List<int> { 1, 2, 3 };
ViewBag.numbers = Numbers;
VIEW
<ul>
#foreach (var number in ViewBag.numbers)
{
<li>#number</li>
}
</ul>
Session is another very useful object that will hold any information.
For instance when user logged in to the system you want to hold his authorization level.
// GetUserAuthorizationLevel - some method that returns int value for user authorization level.
Session["AuthorizationLevel"] = GetUserAuthorizationLevel(userID);
This information will be stored in Session as long as user session is active.
This can be changed in Web.config file:
<system.web>
<sessionState mode="InProc" timeout="30"/>
So then in controller inside the action :
public ActionResult LevelAccess()
{
if (Session["AuthorizationLevel"].Equals(1))
{
return View("Level1");
}
if (Session["AuthorizationLevel"].Equals(2))
{
return View("Level2");
}
return View("AccessDenied");
}
TempData is very similar to ViewData and ViewBag however it will contain data only for one request.
CONTROLLER
// You created a method to add new client.
TempData["ClientAdded"] = "Client has been added";
VIEW
#if (TempData["ClientAdded"] != null)
{
<h3>#TempData["ClientAdded"] </h3>
}
TempData is useful when you want to pass some information from View to Controller. For instance you want to hold time when view was requested.
VIEW
#{
TempData["DateOfViewWasAccessed"] = DateTime.Now;
}
CONTROLLER
if (TempData["DateOfViewWasAccessed"] != null)
{
DateTime time = DateTime.Parse(TempData["DateOfViewWasAccessed"].ToString());
}
ViewBag, ViewData, TempData, Session - how and when to use them?
ViewBag
Avoid it. Use a view model when you can.
The reason is that when you use dynamic properties you will not get compilation errors. It's really easy to change a property name by accident or by purpose and then forget to update all usages.
If you use a ViewModel you won't have that problem. A view model also moves the responsibility of adapting the "M" (i.e. business entities) in MVC from the controller and the view to the ViewModel, thus you get cleaner code with clear responsibilities.
Action
public ActionResult Index()
{
ViewBag.SomeProperty = "Hello";
return View();
}
View (razor syntax)
#ViewBag.SomeProperty
ViewData
Avoit it. Use a view model when you can. Same reason as for ViewBag.
Action
public ActionResult Index()
{
ViewData["SomeProperty"] = "Hello";
return View();
}
View (razor syntax):
#ViewData["SomeProperty"]
Temp data
Everything that you store in TempData will stay in tempdata until you read it, no matter if there are one or several HTTP requests in between.
Actions
public ActionResult Index()
{
TempData["SomeName"] = "Hello";
return RedirectToAction("Details");
}
public ActionResult Details()
{
var someName = TempData["SomeName"];
}
TempData
is meant to be a very short-lived instance, and you should only use it during the current and the subsequent requests only! Since TempData works this way, you need to know for sure what the next request will be, and redirecting to another view is the only time you can guarantee this. Therefore, the only scenario where using TempData will reliably work is when you are redirecting. This is because a redirect kills the current request (and sends HTTP status code 302 Object Moved to the client), then creates a new request on the server to serve the redirected view. Looking back at the previous HomeController code sample means that the TempData object could yield results differently than expected because the next request origin can't be guaranteed. For example, the next request can originate from a completely different machine and browser instance.
ViewData
ViewData is a dictionary object that you put data into, which then becomes available to the view. ViewData is a derivative of the ViewDataDictionary class, so you can access by the familiar "key/value" syntax.
ViewBag
The ViewBag object is a wrapper around the ViewData object that allows you to create dynamic properties for the ViewBag.
public class HomeController : Controller
{
// ViewBag & ViewData sample
public ActionResult Index()
{
var featuredProduct = new Product
{
Name = "Special Cupcake Assortment!",
Description = "Delectable vanilla and chocolate cupcakes",
CreationDate = DateTime.Today,
ExpirationDate = DateTime.Today.AddDays(7),
ImageName = "cupcakes.jpg",
Price = 5.99M,
QtyOnHand = 12
};
ViewData["FeaturedProduct"] = featuredProduct;
ViewBag.Product = featuredProduct;
TempData["FeaturedProduct"] = featuredProduct;
return View();
}
}
namespace TempData.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
TempData["hello"] = "test"; // still alive
return RedirectToAction("About");
}
public ActionResult About()
{
//ViewBag.Message = "Your application description page.";
var sonename = TempData["hello"]; // still alive (second time)
return RedirectToAction("Contact");
}
public ActionResult Contact()
{
var scondtime = TempData["hello"]; // still alive(third time)
return View();
}
public ActionResult afterpagerender()
{
var scondtime = TempData["hello"];//now temp data value becomes null
return View();
}
}
}
In above conversation, there is little confuse for everyone. if you look at my above code, tempdata is like viewdata concept but then it is able to redirect other view also. this is first point.
second point:
few of them are saying it maintains value till read and few of them are asking that so will it read only time? not so.
Actually, you can read any number of time inside your code in one postpack before page render. once page render happened, if you do again postpack (request) means, the tempdata value becomes NULL.
even you are requesting so many time , but the tempdata value is still alive -->this case your number of request should not read temp data value.
In simple terms:
ViewBag is a dynamic (dynamic: ability to assign more than one value by different programs on the same object) object which is used to send data from controller to view. It can be used when we jump from controller's action to some view. However the value which we get in the view(in the viewbag object) can not be further replicated in other view/controller/js page etc. Meaning: Controller->View (possible). Now this value can not be send to next view/controller. Meaning Controller->View->View/controller/some js file (not possible), to carry this value you need to define some other variable and store viewbag value into it and then send it as a parameter.
Tempdata is very much different than viewbag. It has nothing to do with view at all. It is used when we send data from one action(method) to other action in the same controller. That's the reason we use RedirectToAction whenever sending tempdata value from one action to another. Value of tempdata are not retained when send from controller to veiw (because it is not meant to do so).
ViewData is simillar to viewbag but is a dictionary object. ViewData may require type casting while viewbag may not. It depends on the user which one he may want to use.
I am working on a project on asp.net MVC3, I have a controller named UserProfile when i run my project and login, it shows error A public action method images was not found on controller UserProfile
I don't have any action method named images in any of my controllers,below is my UserProfile's index method
[CustomAuthorizeAttribute]
public ActionResult Index()
{
var userName = string.Empty;
if (SessionHelper.GetSession("login") != null)
{ userName = SessionHelper.GetSession("login").ToString(); }
else
{ return View(); }
SessionHelper.SetSess("issetup", null);
UserProfileModel model = GetUserProfileData(userName);
StateandCityDropdown();
return View(model);
}
I have two forms on userprofile index view one with some textboxes and other fields for entering data and second for uploading images
It sounds to me like the routes are picking up on a url you have and mistaking them for an action. It could be that you have a link to an image directory underneath a directory that matches your controller such as /User/Images would thow this error because the routing would then expect you to have an Images action when you dont. Check the page source for anything linking to an images folder but without an image included. The other option is that the routes are picking up the images as well as the actions you want them to. If this is the case in your Global.asax.cs file check the RegisterRoutes method has some ignores in for images.
routes.Ignore("{*allpng}", new { allpng = #".*\.png(/.*)?" });
routes.Ignore("{*allgif}", new { allgif = #".*\.gif(/.*)?" });
routes.Ignore("{*alljpg}", new { alljpg = #".*\.jpg(/.*)?" });
Hope this helps
Andy
I am absolute beginner in C#, ASP.NET and MVC2 and this means that I just might be missing something exceptionally basic. I tried to search for it, but here again, I failed to come up with the proper incantations for neither Google not StackOverflow, so here comes the question:
I am trying to create a basic controller with two actions:
[HttpPost]
public ViewResult Create(CustomerCreateData data)
{
CustomerRecord cr = //create customer record from input data...
return RedirectToAction("Details");
}
public ViewResult Details(int id)
{
CustomerRecord cr = // load customer record with specified id...
return View(cr);
}
My idea is thet after successful POST /Customer/Create the user would be redirected to GET /Customer/Details/42 where 42 is id of the newly created customer record.
What is the proper incantation for this in ASP.NET MVC2
PS - I've seen countless examples of redirecting to "Index" action, but that is not quite enough.
You can pass data to the RedirectToAction method:
return RedirectToAction("Details", new { id = cr.Id });
This is assuming you either have a defined route, e.g. Customer/Details/{id} or that you still have the default route {controller}/{action}/{id}.
In Create ActionResult, after creation success make an action like this (or realy this for example):
return RedirectToAction("Details", new { Id = cr.Id });
This code produce a redirect to Details/id/{cr.Id}.
Sorry for bad english (i'm italian)
I have the following two routes defined in my MVC app.;-
At the moment I have two "MVC View content pages" defined
/ShowName/NameById
/ShowName/Index
However the content on these two pages are identical? Is it possible for two routes to share the same content page? If not then can I a) create a single rule for both routes or b) should I create a usercontrol to share between both content pages to show my data?
routes.MapRoute(
"NameById",
"Name/{theName}/{nameId}",
new
{
action = "NameById",
controller = "ShowName",
theName = "Charley"
}
,new { nameId = #"\d+" }
);
routes.MapRoute(
"ShowName",
"Name/{theName}",
new
{
action = "Index",
controller = "ShowName",
theName = "Charley"
}
);
EDIT
I have read the answers below and I have the following action result methods. If I remove one of the methods (e.g. the Index) then how would I rewrite my routes to a single route?
public ActionResult Index(string theName)
public ActionResult NameById(string theName, int? nameId)
So the following works url's work?
/Name/Charley
/Name/Charley/11234
You could create a partial view for the detail area of the page, keeping the separation of the two actions in case they change at a later point. Or you could just
return View("DetailView", model);
But that can introduce an extra string to manage between the two controller actions. Since MVC does not support overloading by action name (unless you have a GET/POST pair, one with no arguments), you could just check the {nameId} parameter and see if it is empty/null before using it.
Do you really need 2 different routes? You could make the pattern for your Index route
Name/{theName}/{nameId}
and make nameId a nullable input to your action. Then just add some logic to your action which checks whether nameId has a value and acts accordingly.
Okay so, i am totally new to MVC and I'm trying to wrap my head around a few of the concepts. I've created a small application...
This application has a view for creating a new Individual record. The view is bound to a model ViewPage... And I have a associated IndividualController which has a New method...
The New method of the IndividualController looks like this...
public ActionResult New()
{
var i = new Individual();
this.Title = "Create new individual...";
i.Id = Guid.NewGuid();
this.ViewData.Model = new Individual();
return View();
}
Now, the above all seems to be working. When the view loads I am able to retrieve the data from the Individual object. The issue comes into play when I try and save the data back through the controller...
In my IndividualController I also have a Save method which accepts an incoming parameter of type Individual. The method looks like...
public ActionResult Save(IndividualService.Individual Individual)
{
return RedirectToAction("New");
}
Now, on my view I wanted to use a standard html link/href to be used as the "Save" button so I defined an ActionLink like so...
<%=Html.ActionLink("Save", "Save") %>
Also, defined in my view I have created a single textbox to hold the first name as a test like so...
<% using (Html.BeginForm()) { %>
<%=Html.TextBox("FirstName", ViewData.Model.FirstName)%>
<% } %>
So, if I put a break point in the Save method and click the "Save" link in my view the break point is hit within my controller. The issue is that the input parameter of the Save method is null; even if I type a value into the first name textbox...
Obviously I am doing something completely wrong. Can someone set me straight...
Thanks in advance...
Your New controller method doesn't need to create an individual, you probably just want it to set the title and return the view, although you may need to do some authorization processing. Here's an example from one of my projects:
[AcceptVerbs( HttpVerbs.Get )]
[Authorization( Roles = "SuperUser, EditEvent, EditMasterEvent")]
public ActionResult New()
{
ViewData["Title"] = "New Event";
if (this.IsMasterEditAllowed())
{
ViewData["ShowNewMaster"] = "true";
}
return View();
}
Your Save action should take the inputs from the form and create a new model instance and persist it. My example is a little more complex than what I'd like to post here so I'll try and simplify it. Note that I'm using a FormCollection rather than using model binding, but you should be able to get that to work, too.
[AcceptVerbs( HttpVerbs.Post )]
[Authorization( Roles = "SuperUser, EditEvent, EditMasterEvent")]
public ActionResult Save( FormCollection form )
{
using (DataContext context = ...)
{
Event evt = new Event();
if (!TryUpdateModel( evt, new [] { "EventName", "CategoryID", ... }))
{
this.ModelState.AddModelError( "Could not update model..." );
return View("New"); // back to display errors...
}
context.InsertOnSubmit( evt );
context.SubmitChanges();
return RedirectToAction( "Show", "Event", new { id = evt.EventID } );
}
}
If I don't create a new Indvidual object in the New method then when my view tries to bind the textbox to the associated model I get a NullReferenceException on the below line in my view...
`<%=Html.TextBox("FirstName", ViewData.Model.FirstName)%>`
With regards to the Save method. From what I understand since my view is strongly typed shouldn't I be able to have a method signature like...
`public ActionResult New(IndividualService.Individual ind)
{
return View();
}`
I thought that was the purpose behind model binding..?
I would strongly recommend that you take a step back from what you are doing and run through some of the Tutorials/Videos here http://www.asp.net/learn/
If you have a strongly typed View it means that when the Controller picks that view to generate the output the view has better access to the Model.
However the View is not responsible for what comes back from the client subsequently such as when a form is posted or a URL is otherwise navigated to.
ASP.NET-MVC uses information in the URL to determine which Controller to hand the request off to. After that it's the controller's responsibility to resolve the various other elements in the request into instance(s) of Model classes.
The relationship between the incoming request and the controller is clouded by the significant assistance the ASP.NET-MVC routing gives the controller. For example a route can be defined to supply parameters to the controller method and thats all the controller needs and hence you don't see any code in the method relating to the http request. However it should be understood that the contoller method is simply processing a http request.
I hope you can see from the above that it would be too early in a requests life-cycle for an instance of a class from the model to passed to a public method on a controller. Its up to the controller to determine which model classes if any need instancing.