I have a controller (ResponseResourceController) which Index Action is supposed to receive as parameter int and in the Index action - Parse enum value from Tempdata. Then this action redirects to another action with this two parameters.
public class ResponseResourceController : Controller
{
public async Task<IActionResult> Index(
int id)
{
var entityType = (EntityType)TempData["EntityType"];
var exists = await _responseResourceStringUiService.ResponseResourceStringExistsAsync(
id,
entityType);
return RedirectToRoute(!exists ?
RouteNames.ResponseResourceString_Home_Add :
RouteNames.ResponseResourceString_Home_Edit,
new {id, entityType});
}....
}
public class PublicationController : Controller
{
public async Task<IActionResult> Index()
{
var vm = new PublicationViewModel
{
...
};
TempData["EntityType"] = InboxEntityType.Publication;
........
}
My question is - is there more elegant way to achieve this without the enum value beeing in the url after the redirection.
I was thinking for example to create an abstract property of thids Enum Type in this ResponseResourceController
AND the other controller PublicationController(and othe that are going to do that) to inherit it and override the property with the right enum value and as the ResponseResourceController will have property I will not need the entity type as parameter in the Add and Edit Action.
Why don't just directly call the PublicationController and avoid creating an unnecessary redirect? You can distinguish directly in the frontend if it is a new or an existing record.
If you wanna use this method for creating new records then you should prefer to use a POST request instead of GET.
In short, multiple controllers does not work and url redirection is not necessary.
From the description I think what you really want is displaying the add/edit form base on an ID (with your temp entity type stored in somewhere). If this is what you want then single controller with multiple views should be able to handle that.
What will happen when you create multiple controllers?
The routing rules in the app has to be unique. If you have a ResponseResourceController and a PublicationController and each of them has the Index action, what will be the routes look like?
You probably need to define a route like /ResponseResource/Index for ResponseResourceController.Index() and a route like /Publication/Index for PublicationController.Index(). If there is a EntityType called Bob, you may need to define a route like /Bob/Index and create a BobController.
However, if you try to name a unique route for each of the EntityType, your route has to contain that piece of information (either in the route or in the query string). So, creating a dedicated controller for each EntityType cannot solve your problem.
Multiple views for multiple entity types
If you don't want to include the EntityType in the url, you may use ResponseResourceController.Index(int id) to get the entity ID and respond with different views for different EntityType:
[Route("/response-resource")]
public class ResponseResourceController : Controller
{
[HttpGet("")]
public async Task<IActionResult> Index(int id)
{
var entityType = (EntityType)TempData["EntityType"];
var exists = await _responseResourceStringUiService.ResponseResourceStringExistsAsync(id, entityType);
var viewName = exists ? "_AddEntity" : "_EditEntity";
var vm = new GenericViewModel
{
type = entityType,
/* other fields */
}
return View(viewName, vm); // so user may see a different view under different situation
}
}
And for the view model:
public class GenericViewModel
{
public EntityType type { get; set; }
}
You also need to define 2 views: _AddEntity and _EditEntity. In the above example, GenericViewModel contains the EntityType so you will know which entity you are handling.
Furthermore, you can even define a dedicated view for each EntityType and pass a dedicated view model to that view. e.g. PublicationViewModel will be the view model used in _AddPublication and _EditPublication views.
Expected behavior
When user requests for ResponseResource with and id, e.g. /response-resource?id=123, your application will render the view base on the id and the EntityType. No url redirection and url won't change.
Related
I am trying to come up with the best pattern for passing data to my _layout.cshtml page.
I am toying with creating a common base class from which all view specific models derive. This base class would be recognized by my _layout.cshtml and used to fill in details about the user and load proper images in the header, etc. For example, here is a snippet of it.
public abstract class ViewModelBase
{
public string Username { get; set; }
public string Version { get; set; }
}
At the top of my _layout.cshtml I have...
#model MyProject.Web.Controllers.ViewModelBase
I need a common area to hydrate the information required by the model, and am planning to use the following pattern...
Each action method creates and hydrates a model derived from
ViewModelBase.
The action completes.
I create a ActionFilterAttribute and override OnActionExecuted to cast the
current Result to ViewModelBase.
If the conversion is successful, then I populate the ViewModelBase details with the relevant data.
Here are my questions...
Is the use of a ActionFilterAttribute (OnActionExecuted) a good pattern for what I am trying to do?
I am not able to see how to get the Result created in the action from the HttpActionExecutedContext. How is this done?
I follow the same approach and use a base ViewModel class which all my other viewModels inherit from.
Then, I have a base controller that all controller inherit from. In there, I have one method that takes care of initializing the view model:
protected T CreateViewModel<T>() where T : ViewModel.BaseViewModel, new()
{
var viewModelT = new T {
HeaderTitle = "Welcome to my domain",
VisitorUsername = this.VisitorUsername,
IsCurrentVisitorAuthenticated = this.IsCurrentVisitorAuthenticated,
//...
};
return viewModelT;
}
Then on each controller, when I want to create the view model, I simply call the base controller's method:
var vm = base.CreateViewModel<MyPageCustomViewModel>();
I am rewriting an ASP.NET webforms app in MVC4 and was wondering how to solve the following problem. It is a multi-tenant app, so part of the URL has the tenant NAME in it:
http://mysite/tenant/controller/action
But tenant is an abbreviation representing the tenant, but I'd like to always convert that to the corresponding integer id and use that throughout the code. What is the best way to write that convert code once and have some variable/property available to all controller methods.
public class DivisionController : Controller
{
//
// GET: /Division/
public ActionResult Index()
{
// I want this.TenantId to be available in all controller methods
FetchDivisions(this.TenantId);
return View();
}
Is a base controller the best way to handle this or filters or attributes?
Yes a base controller will handle this just fine. If you need to perform a database lookup to convert the abbreviation to the integer value you can use the OnActionExecuting event like so:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
// Lookup code here.
}
In my application there's the need of passing out a specific parameter, when it exists, through all pages.
What is the best way of I can do this?
There's some function that is called whenever I do an GET or POST requisition, in which I can verify if the parameter exist and persist it?
Create an action filter like the one below.
public sealed class ScaffoldActionFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var model = filterContext.Controller.ViewData.Model as PageModelBase;
// Whatever you want to do.
model.myParam = "Im available in all views";
}
}
Next register it to run on every request. Look in app_start/filterconfig.cs
and and it like this.
filters.Add(new ScaffoldActionFilter());
Now just return the standard pagebasemodel or a derived type from every view and you have what you want.
Previously, I had two methods and I labelled one with [WebGet] and one with [WebInvoke(Method = "POST"]
when I did a GET or a POST to the URL that I specified, it would always call the correct method.
The URLs were:
POST: fish-length
GET: fish-length?start-date={startDate}&pondId={pondId}
Now that I'm using web api, I have to define my routes seperately, like this:
RouteTable.Routes.MapHttpRoute(
name: "AddFishLength",
routeTemplate: "fish-length",
defaults: new
{
controller = "FishApi",
action = "AddFishLength"
});
RouteTable.Routes.MapHttpRoute(
name: "GetFishLength",
routeTemplate: "fish-length?start-date={startDate}&pondId={pondId}",
defaults: new
{
controller = "FishApi",
action = "GetFishLength"
});
However the second route doesn't work, because you're not allowed a ? in the routeTemplate.
I can change the URL format to something like fish-length/{startDate}/{pondId} but it's really not a very nice way to expose the service.
Is there a better way to do this? Also because I was doing a POST and GET to the same url before, I'd need to ensure my routing method still allowed this. Assuming the above worked, I'm still not sure how it would route correctly.
No, you don't need to define separate routes. All you need is a single route:
RouteTable.Routes.MapHttpRoute(
name: "AddFishLength",
routeTemplate: "fish-length",
defaults: new
{
controller = "FishApi",
}
);
and then follow the RESTful naming conventions of your ApiController's actions:
public class FishApiController: ApiController
{
// will be called for GET /fish-length
public HttpResponseMessage Get()
{
// of course this action could take a view model
// and of course that this view model properties
// will automatically be bound from the query string parameters
}
// will be called for POST /fish-length
public HttpResponseMessage Post()
{
// of course this action could take a view model
// and of course that this view model properties
// will automatically be bound from the POST body payload
}
}
So assuming you have a view model:
public class FishViewModel
{
public int PondId { get; set; }
public DateTime StartDate { get; set; }
}
go ahead and modify your controller actions to take this parameter:
public class FishApiController: ApiController
{
// will be called for GET /fish-length
public HttpResponseMessage Get(FishViewModel model)
{
}
// will be called for POST /fish-length
public HttpResponseMessage Post(FishViewModel model)
{
}
}
You could obviously have different view models for the different actions.
You can't specify the query string parameters in the route template - but as long as you have a method that matches the names of the parameters WebApi should be clever enough to figure it out on its own.
public HttpResponseMessage Get(string id) would correspond to a request for {controller}?id=xxx
However, it's hard to tell without seeing the actual objects how you should go about solving your case. E.g. WebApi doesn't like complex types in a Get request, as well as it supports url-encoded content in post data in a particular way only.
As for differentiating between Get and Post that's quite simple - WebApi knows which method you used when sending the request and then it looks for a method name starting with Get/Post or decorated with the HttpGet/Post attribute.
I recommend taking a look at the following articles - they helped me understand how it works:
http://www.west-wind.com/weblog/posts/2012/Aug/16/Mapping-UrlEncoded-POST-Values-in-ASPNET-Web-API
http://blogs.msdn.com/b/jmstall/archive/2012/04/16/how-webapi-does-parameter-binding.aspx
Looking for some guidance in designing my new MVC 4 app.
I would like to have a url parameter s=2011 on every page of the app to let me know what year of data I'm working with. Obviously, the user will have a way to change that parameter as needed.
I will need that parameter in every controller and wondering the best way to do this. I was thinking of creating a base controller that reads Request.QueryString and puts the year into a public property. However, considering all the extensability points in MVC, I'm wondering if there's a better way to do this?
This very much depends on the design of your app, but just to give you two alternatives
IActionFilter
If you are doing data context per request you can use a global IActionFilter to hook pre-action execution globally and apply a query filter to your data context behind the scenes.
Major down-side of this is that to test the controller you will need to have the full MVC pipeline setup so that the actionfilter gets applied properly.
Dependency Injection
Instead of using sub-classing (base controller as you say) you can use dependency injection . Keeping things more loose will allow you to pull the filter from query string, cookie, user setting in the database or whatever else - without your controller knowing where it comes from.
Here is some pseudo code how I would do it if I was using something like Entity Framework or Nhibernate (also I am sure applicable with other technologies as well)
public Car
{
public string Year { get; set; }
}
public class CarsDataContext : DbContext
{
private IQuerable<Cars> _cars = null;
private Func<Car, bool> _carsFilter = null;
public IQuerable<Car> Cars {
get {
if (_carsFitler != null)
return _cars.Where(_carsFitler);
return _cars;
}
set { _cars = value; }
}
public void ApplyCarsFilter(Func<Car, bool> predicate)
{
_carsFilter = predicate;
}
}
Assuming you have dependency injection setup already (NInject or whichever other framework) in you can configure how the context to be intialized
Bind<CarsDataContext>().ToMethod(() => {
string yearFilter = GetYearFilter(); // can be coming from anywhere
CarsDataContext dataContext = new CarsDataContext();
dataContext.Applyfilter(car => car.Year == yearFilter);
return dataContext;
}).InRequestScope();
Then my controller knows nothing about the data filtering and I can easily test it:
class MyController : Controller
{
public MyController(CarsDataContext dataContext)
{
}
...
}
However I would only do this is filtering the dataset was across many controllers and important part of my software. Otherwise it's pure over-engineering.