ASP.NET MVC Routes and Client Specifc Functions - asp.net-mvc-3

I'm working on an application where a system is being built where multiple "customers" will use the system and 99.9% of the controllers/actions will be the same, just pulling different data, but there are times and places where a custom controller action, or view might be needed.
Right now I'm using a default route similar to the following to get the company name with requests.
routes.MapRoute(
"Default", // Route name
"{company}/{controller}/{action}/{id}",
new {company = "Unknown", controller = "Home",
action = "Index", id = UrlParameter.Optional}
);
This works great as I can have my individual controller actions defined like this
public ActionResult ShowReport(string company)
{
//Actual code goes here..
}
I have a system in place that will get the data segment for this specific company and return the proper view. SO for my 99.9% situation this looks great. What I'm looking for is a solution for when I need to render a different view, or have additional actions that are specific to one company.
I could add in switch or other logic within my action, but that feels overly dirty...

For a specific company you can use something like this and put it before the default action, in this case url has to contain Company1/somethingcontroller/etc/etc.
routes.MapRoute(
"Company1Default", // Route name
"Company1/{controller}/{action}/{id}",
new {company = "Company1", controller = "DefaultControllerForCompany1",
action = "Index", id = UrlParameter.Optional}
);

While I actually lean to Jay's answer pertaining to the use of data within models, I think that there is another option. Be warned that I haven't played this all the way out and don't have a full understanding of your application...
Why would you want to hardcode a company name within your global.asax? I don't think that it would be very scalable. If you want to add support for an additional 10 companies, you'd have to create 10 new entries. Also, what if you want to change the name of a company because of a buyout or something? More maintenance.
Why not add a route to send every company to the same controller like...
routes.MapRoute(
"CompanyRouting", // Route name
"{companyname}/{action}",
new { controller = "MySingleCompanyControllerName", action = "Index", companyname = UrlParameter.Optional }
);
MySingleCompanyController.cs
Once in your controller you can just get whatever the companyname value is whenever you want it.
public ActionResult Index()
{
ViewData["companynamevalue"] = RouteData.Values["companyname"];
return View();
}
Index.aspx
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Index</title>
</head>
<body>
<div>
Requested Company Name = <%: ViewData["companynamevalue"] %>
</div>
</body>
</html>
NOTE: One more thing to look into for routing help is Phil Haack's routing debugger.

Defining which view is return from an action is easy:
return View("Index");
This will return the view named "Index" no matter which action is invoked.
On the other hand, both views and actions must be defined at compile time, so you cannot dynamically create them (from what i know of).
I might suggest implementing the Command Pattern in your action to get exactly what you would want for individual companies from a single action.

Related

Why is my textBoxFor using my route data?

So I have these three lines:
<div style="background-color: lightgreen;">#Html.TextBoxFor(m => m.Id)</div>
<div style="background-color: green;">#Html.DisplayTextFor(m => m.Id)</div>
<div style="background-color: pink;">#Model.Id</div>
I've identified that the lightgreen value is not my Model.Id but the Id that is set by my route:
routes.MapRoute("Default", "{controller}/{action}/{id}", new { controller = "MyFunController", action = "Index", id = UrlParameter.Optional });
I've come accross some explanations here:
http://forums.asp.net/t/1792086.aspx/1
http://www.hanselman.com/blog/TheWeeklySourceCode38ASPNETMVCBetaObscurityModelStateIsValidIsFalseBecauseModelBinderPullsValuesFromRouteData.aspx
http://ayende.com/blog/3683/reproducing-a-bug
But they have all left me on my appetite. I'm looking for a smart way to work around this, I don't want to change my model's property names nor do I want to change the name of the route item. If I do it will represent a lot of work for me and is not ideal.
I'm sure I'm not the only one with this issue?
(This is MVC 4)
Thanks!
You could remove the problematic value from the ModelState (which is where the Html helpers are taking it from) in the controller action that is rendering the view:
public ActionResult SomeAction(int id)
{
ModelState.Remove("Id");
MyViewModel model = ...
return View(model);
}
Now it's the Id property of your view model that's gonna get used by the TextBox and not the one coming from the route.
Obviously that's only an ugly horrible workaround. The correct way is to of course properly define your view models so that you do not have such naming collisions.

Basic MVC routing query using default project template

I am in the process of learning MVC 3 using the basic project template coupled with several examples I have. Things are going well, but now I am trying to implement my controllers and I am having a couple of issues.
So far I have modified the _Layout.cshtml file to have a new link with a specified route defined:
<header>
<div id="title">
<h1>My MVC Application</h1>
</div>
<div id="logindisplay">
#Html.Partial("_LogOnPartial")
</div>
<nav>
<ul id="menu">
<li>#Html.ActionLink("Home", "Index", "Home")</li>
<li>#Html.RouteLink("Contracts", "Contract")</li>
<li>#Html.ActionLink("About", "About", "Home")</li>
</ul>
</nav>
</header>
and my global.asax.cs file is as follows:
routes.MapRoute(
"Contract",
"Contract",
new { controller = "Contract", action = "List", id = UrlParameter.Optional }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
This works fine as in it returns the expected action view from my Contract controller.
However I would like to modify this to accept an id into the List action. I know that I need to change the List method to accept a parameter, no problem there, but the issue it with the route and how to pass this paramter into the List method from the RouteLink in the _Layout.cshtml file. I have tried a few things, but this bit is really stumping me.
I intend to pass an id from the User that I logged in as through the AccountController, however I will ask another question about that to keep this more consise.
Thank you very much.
You don't actually need your Contract route, as your Default route will work for any controller and action that corresponds to the pattern controller/action/(optional id parameter here). See the comment in the template actually says Parameter defaults. This means, if there is no Controller, Action, or id passed in, it will default to those values. That's why you can just browse to the root of the website and the Home controller's Index action is the default call.
When using routes, you need to remember that the route parameter names need to match the parameter names in your actions.. for example, your Default route currently lets you do this:
[HttpGet]
public ActionResult MyAction(int id) {
}
But, if you changed your default route to be this:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{myIDParameter}", // URL with parameters
new { controller = "Home", action = "Index", myIDParameter = UrlParameter.Optional } // Parameter defaults
);
..your Index action would no longer bind the integer parameter properly.. you would have to change the action to this:
[HttpGet]
public ActionResult MyAction(int myIDParameter) {
}
In answer to your question, it might make more sense to use an ActionLink, like the other two you already have:
#Html.ActionLink("Contracts", "Contract", "ActionMethodHere", new { id = UserIdHere }, null)
That assumes though, that you remove your Contract route and just use the default route.

MVC routing issues, areas and HTML actionlinks

I've looked for this for a while and the solutions look like they should work, it appears I'm doing it wrong.
I created an area for administering the CMS side called "manage" so if you go to:
/Manage/Vinyard
it will give you a list of vinyards to manage using VinyardController built with the CRUD scaffold.
On the "front end" I have a browse controller and another VinyardController for viewing the details of a vinyard.
So someone goes to
/Browse/Vinyard
it gives them a list of Vinyards, they click on one (here's the problem) I want it to go to
/Vinyard/NameOfVinyard
The route that I have is:
routes.MapRoute(
"Vinyard",
"Vinyard/{Name}",
new { controller = "VinyardController", action = "Details", area="root"}
);
Which is above the default route. Details is the method that displays the Vinyard details.
the HTML.actionlink I'm using is:
#Html.ActionLink(item.Name, "Details", "vinyard" ,new { name = item.VinyardId, area="root" })
for some reason the a tag that's returned is: /Browse/Details?Length=7
On top of that when I try to browse to /vinyard/1 it gives me a 404.
Thanks for your help!
Update: If I browse to /vinyard/details/1 it works properly, except that I want it to eschew the /details/ part.
Use this overload
public static MvcHtmlString ActionLink(
this HtmlHelper htmlHelper,
string linkText,
string actionName,
string controllerName,
Object routeValues,
Object htmlAttributes
)
So change your code to
#Html.ActionLink(item.Name, "Details", "vinyard" ,
new { name = item.VinyardId, area="root" },null)
Fixed it. Working with Shyju's modified action link, but I also removed the area property and had to fix up the map routing for it to work right.
My global.ascx now looks like this:
routes.MapRoute(
"Vinyard",
"Vinyard/{id}",
new { controller = "Vinyard", action = "Details", id=UrlParameter.Optional},
new[] { "MyNameSpace.Controllers" }
);
The two problems were: my controller name needed to be "Vinyard" not "VinyardController" and I needed to add the name space here and in my area route registration since I was using the same class names in both areas.

Retrieve form data in action method : ASP.NET MVC 3

I am trying to use a simple form with only a text field to get some information that will be used in an action method to redirect to a different action method. Here's the context:
I have a route mapped in my global.asax.cs file which prints "moo" the given amount of times. For example, if you typed "www.cows.com/Moo8", "Moo" would be printed 8 times. The number is arbitrary and will print however many "Moo"s as the number in the URL. I also have a form on the homepage set up as follows:
#using (Html.BeginForm("Moo", "Web"))
{
<text>How many times do you want to moo?</text>
<input type="text" name="mooNumber" />
<input type="submit" value="Moo!" />
}
The number submitted in the form should be sent to the action method "Moo" in the "Web" controller (WebController.cs):
[HttpPost]
public ActionResult Moo(int mooNumber)
{
Console.WriteLine(mooNumber);
return RedirectToAction("ExtendedMoo", new { mooMultiplier = mooNumber });
}
Finally, the "Moo" action method should send me back to the original "www.cows.com/Moo8" page; as you can see above I simply used an already existing action method "ExtendedMoo":
public ViewResult ExtendedMoo(int mooMultiplier)
{
ViewBag.MooMultiplier = RouteData.Values["mooMultiplier"];
return View();
}
How can I access the value submitted in my form and use it in the last call to "ExtendedMoo"?
Refer to this post or this, you might get some idea how routing works. Something is wrong with "www.cows.com/Moo8", try to find it out. Hint "{controller}/{action}/{parameter_or_id}"
Instead of RedirectToAction, use Redirect and create the Url.
This should do the trick:
return Redirect(Url.RouteUrl(new { controller = "Web", action = "ExtendedMoo", mooMultiplier = mooNumber }));
I hope i helps.
Oh wow. Turns out that form was on my Homepage, so instead of using "Moo" as the action method, I needed to override the "Homepage" action method with a [HttpPost] annotation over THAT one. Didn't realize that forms submitted to the page they were rendered from - that was a really useful piece of information in solving this problem!
Thanks all for your attempts at helping out!
If I understood right
You can you use form Collection to get the value from textbox.
Make Sure the input tag has both id and name properties mentioned otherwise it wont be available in form collection.
[HttpPost]
public ActionResult Moo(int mooNumber, **formcollection fc**)
{
**string textBoxVal= fc.getvalue("mooNumber").AttemptedValue;**
Console.WriteLine(mooNumber);
return RedirectToAction("ExtendedMoo", new { mooMultiplier = mooNumber });
}

MVC Separation of Concerns

so I was going on creating my MVC 3 web app when it dawned on me that I might be putting entirely too much logic in my Controller, that it needs to be in the Model instead. The problem with this is, that in this particular instance I'm dealing with a file.
The SQL database table stores the path of the file, and the file itself is saved in a directory. So in the database, the file path is stored as an nvarchar, and in the model, the file is a string, everything's consistent to that point. The issue comes when it's time to upload the file, at that point I'm dealing with a System.IO.File.
So the question is, how do you provide System.IO.File logic inside the model for the file when in the back-end it is actually a string?
I had finished the functional version of the Controller and had some logic already in it, and was about to add more when I realized that I was working against the system. What I mean is that in order to have server-side validation, the logic needs to be in the Model in order for the input validation to behave and work according to proper MVC rules, obviously optionally using client-side validation in conjunction.
Currently...
Here is my View:
#model ProDevPortMVC3.Profile
#{
ViewBag.Title = "Profile Photo Upload";
}
<h2>Photo Upload</h2>
<img alt="Profile Image" src="#Html.DisplayFor(model => model.ProfilePhotoPath)" />
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm("UploadPhoto", "Profile", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.ValidationSummary(true)
<br />
<input type="file" name="File1" />
#Html.ValidationMessageFor(model => model.ProfilePhotoPath)
<input type="submit" value="Upload" />
}
Here is my Controller (just the relevant action method):
[HttpPost]
public ActionResult UploadPhoto(int id, FormCollection form)
{
Profile profile = db.Profiles.Find(id);
var file = Request.Files[0];
if (file != null && file.ContentLength > 0)
{
try
{
string newFile = Path.GetFileName(file.FileName);
file.SaveAs(Server.MapPath("/Content/users/" + User.Identity.Name + "/" + newFile));
profile.ProfilePhotoPath = "/Content/users/" + User.Identity.Name + "/" + newFile;
UpdateModel(profile);
db.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
return View();
}
And here is my Model (just the part relevant to the file):
public string ProfilePhotoPath { get; set; }
So I guess, what are your guys' solutions in these particular situations?
Description
Assuming i understand your question. I have read your question a few times. ;) If i don't understand, please comment my answer in order to get a better answer (i will update)
I think that you want is.. How to Model Validation for your particular case.
You can add Model Validation errors using the ModelState.AddModelError("Key", "Message) method.
ModelState.AddModelError Adds a model error to the errors collection for the model-state dictionary.
Sample
ModelState.AddModelError("ProfilePhotoName", "YourMessage");
This will affect ModelState.IsValid
So you can do whatever you want (your logic) and can make your Model invalid.
More Information
MSDN - ModelStateDictionary.AddModelError Method
There are any number of answers to this question. I'll take a crack at it knowing the risk going in due to varying opinion. In my personal experience with MVC3 I like to use flatter, simpler Models. If there is validation that can be done easily in a few lines of code that doesn't require external dependencies then I'll do those in the Model. I don't feel like your System.IO logic is validation, per se. Validation that could go in the Model, in my mind, is whether the filename is zero length or not. The logic to save is something you can put in your controller. Better yet, you could inject that logic using the Inversion of Controller pattern and specifically a Dependency Injection solution.

Resources