Suppose I have a CategoriesController for managing some kind of categories list. It has these action methods:
public ActionResult Get(Int32 categoryId);
public ActionResult Edit(Int32 categoryId);
[HttpPost]
public ActionResult Edit(Int32 categoryId, CategoryModel model);
public ActionResult Delete(Int32 categoryId);
[HttpPost]
public ActionResult Delete(Int32 categoryId, DeleteModel model);
All of these methods run the same code that verifies that categoryId is valid and the category exists, it retrieves it from the database, and performs access-level verification. Parts of the verification code returns an ActionResult directly, others do not (for example, if the categoryId doesn't exist, then it will return a Http404 action result immediately, but if everything's okay then it goes down to the action-specific code.
Is the best way to cut down on code duplication to do something like this?
private ActionResult EnsureCategory(Int32 categoryId, out DBCategory dbCategory);
public ActionResult Edit(Int32 categoryId) {
DBCategory dbCategory;
ActionResult error = EnsureCategory(categoryId, out dbCategory);
if( error != null ) return error;
// this now means that only 3 lines of code will be shared between the Action methods, but it still seems too much.
}
It's a shame C# doesn't support preprocessor macros, or something like it, because this would be a good place to use them. Unless there's a better approach?
Use action filters, do your validation inside OnActionExecuting method.
I ended up continuing to use the approach I suggested to myself in my original question. I did consider using #SteveB's T4 template suggestion, but I find T4 templates a bit hard to use.
Related
I have two action methods :
public ActionResult Edit(int id)
{
}
public ActionResult Edit(Modelname model, string[] strParam)
{
}
And I am calling the Edit(Modelname,string[]) from the unit test.
var actionResult = controller.Edit(model, strParam);
The code compiles at runtime , but when i debug the test method it gives me a method not found "MissingMethodException" . I tried commenting the Edit(int id) method and then debugging, still the same thing. Other tests are running fine, any help appreciated.
You have an ambiguous match for action methods in your controller. While it will compile just fine, ASP.NET MVC can't decide which method to use at runtime and it throws an exception. You need to make sure they respond to different type of HTTP requests or rename one of them.
I can't be sure with the info you provided but if the second method is processing a POST request, using HttpPost filter will solve the problem:
public ActionResult Edit(int id)
{
}
[HttpPost]
public ActionResult Edit(Modelname model, string[] strParam)
{
}
If that's not the case, renaming is the other solution. If you have a good reason not to do that, ASP.NET MVC provides ActionName filter to override the method name for ASP.NET MVC pipeline:
public ActionResult Edit(int id)
{
}
[ActionName("EditModel")]
public ActionResult Edit(Modelname model, string[] strParam)
{
}
This will make http://example.org/controller/EditModel hit the second method.
I'm using ASP.NET MVC + C# and I have two controller action as follows:
public ActionResult Edit(Guid? id)
{
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(SomeModel model)
{
}
Now, in the browser I can navigate to the [GET] /controller/edit with no id and get an null argument exception (as expected), but what I'd like to know is how do I replicate this scenario in a unit test?
I have a GET and a POST version of the edit method so I can't feed controller.Edit(null) to test it as the code won't compile.
Thanks in advance!
One option would be to use the ActionName attribute to keep the URL the same, but have different method names.
[HttpGet]
[ActionName("Edit")]
public ActionResult GetEditForm(Guid? id)
{
...
}
[HttpPost]
[ActionName("Edit")]
public ActionResult SaveEditForm(SomeModel model)
{
...
}
Then you could of course call the right method from your unit test, and the routing bits of ASP.NET take care of getting the right method called.
For a project I'm currently working on, I currently have 2 separate instances of users (might increase later): CorporateCustomer and PrivateCustomer.
Both inherit from the abstract class Customer.
To display the differences between these customers, currently 2 different views are created, which are rendered by the same Action in the following way:
[HttpGet]
public virtual ActionResult Edit()
{
if(User.IsCorporate)
return View("EditCorporate", new CorporateCustomer());
else
return View("EditPrivate", new PrivateCustomer());
}
[HttpPost]
public virtual ActionResult Edit(CorporateCustomer customer){...}
[HttpPost]
public virtual ActionResult Edit(PrivateCustomer customer){...}
For just displaying information, this works like a charm. The urls are the same for each type, which is what we were aiming for.
However, when doing a post, I can only specify a single type, or risk running into an ambiguous action method (which makes sense, of course).
Now my question is: is there any elegant way to handle these 2 different types, while still retaining a single url? Or am I doomed to make the base class non-abstract and look up the values in the Request.Form collection?
Thanks if anyone can come up with a sollution (or just straight point out that what I'm doing is stupid and cannot be done)
You could have one Action that takes both parameter types.
The model binder should then fill them with whatever data is posted and you can figure out which is right in your Action method.
[HttpPost]
public virtual ActionResult Edit( CorporateCustomer c, PrivateCustomer p )
{
...
}
Wanted to use the same URL for a GET/PUT/DELETE/POST for a REST based API, but when the only thing different about the Actions is which HTTP verbs it accepts, it considers them to be duplicate!
"Type already defines a member called 'Index' with the same parameter types."
To which I said, so what? This one only accepts GET, this one only accepts POST... should be able to be co-exist right?
How?
That's not ASP.NET MVC limitation or whatever. It's .NET and how classes work: no matter how hard you try, you cannot have two methods with the same name on the same class which take the same parameters. You could cheat using the [ActionName] attribute:
[HttpGet]
[ActionName("Foo")]
public ActionResult GetMe()
{
...
}
[HttpPut]
[ActionName("Foo")]
public ActionResult PutMe()
{
...
}
[HttpDelete]
[ActionName("Foo")]
public ActionResult DeleteMe()
{
...
}
[HttpPost]
[ActionName("Foo")]
public ActionResult PostMe()
{
...
}
Of course in a real RESTFul application the different verbs would take different parameters as well, so you will seldom have such situations.
You may take a look at SimplyRestful for some ideas about how your routes could be organized.
While ASP.NET MVC will allow you to have two actions with the same name, .NET won't allow you to have two methods with the same signature - i.e. the same name and parameters.
You will need to name the methods differently use the ActionName attribute to tell ASP.NET MVC that they're actually the same action.
That said, if you're talking about a GET and a POST, this problem will likely go away, as the POST action will take more parameters than the GET and therefore be distinguishable.
So, you need either:
[HttpGet]
public ActionResult ActionName() {...}
[HttpPost, ActionName("ActionName")]
public ActionResult ActionNamePost() {...}
Or:
[HttpGet]
public ActionResult ActionName() {...}
[HttpPost]
public ActionResult ActionName(string aParameter) {...}
Another option is to have a single method that accepts all and distinguishes between HttpMethod and calls the appropriate code from there. E.g.
string httpMethod = Request.HttpMethod.ToUpperInvariant();
switch (httpMethod)
{
case "GET":
return GetResponse();
case "POST":
return PostResponse();
default:
throw new ApplicationException(string.Format("Unsupported HttpMethod {0}.", httpMethod));
}
As a workaround you can add to one of the methods an extra argument with a default value, just to bypass the limitation and be able to build.
Of course take in mind that this is not the most recommended way of doing things, and also you will have to make clear in your code (by the parameter name or via comments) that this is an extra argument just to allow it to build, and of course make sure that you have decorated your attributes correctly.
I'm pretty new to MVC and can't find an answer one way or another to this question. Is there a built in architecture in MVC 1 (or 2, I suppose) that allows you to specify a route mapping via an attribute on a specific action method, rather than in the Global.asax? I can see its use being limited to a degree as multiple methods can be tied to the same action thusly requiring routes to be unnecessarily duplicated, but my question still remains.
Also, does anyone see any gotcha's in implementing something like this, aside from the one I just mentioned about the same action on multiple methods?
Note: I'm not asking HOW to implement this. Only checking if something like this exists, and if not, if it's more trouble than it's worth.
You can also try AttributeRouting, which is available via NuGet. Disclosure -- I am the project author. I've used this in personal and professional projects with great success and would not go back to the default routing mechanism of ASP.NET MVC unless I had to. Take a look at the github wiki. There's extensive documentation of the many features there.
Simple usage looks like this:
public class RestfulTestController : Controller
{
[GET("Resources")]
public ActionResult Index()
{
return Content("");
}
[POST("Resources")]
public ActionResult Create()
{
return Content("");
}
[PUT("Resources/{id}")]
public ActionResult Update(int id)
{
return Content("");
}
[DELETE("Resources/{id}")]
public ActionResult Destroy(int id)
{
return Content("");
}
}
AttributeRouting is highly configurable and has a few extension points. Check it out.
I would recommend ASP.NET MVC Attribute Based Route Mapper for this. This is third party library and does not come with ASP.NET MVC 1 or 2. Usage is like the following:
public SiteController : Controller
{
[Url("")]
public ActionResult Home()
{
return View();
}
[Url("about")]
public ActionResult AboutUs()
{
return View();
}
[Url("store/{category?}")]
public ActionResult Products(string category)
{
return View();
}
}
Then in your global.asax, you just call routes.MapRoutes() to register your action routes.
It's dead simple to implement this.