If I have the following code (EDIT: Sorry if I wasn't clear, I want to encapsulate the following (forget about the view its calling), so that I could do other stuff within the ActionResult):
public ActionResult ModelBased(string[] items, PostedItems postedItems) {
var model = new ItemsViewModel();
var selectedItems = new List<Item>();
var postedItemIDs = new string[0];
if (postedItems == null) postedItems = new PostedItems();
if (items!= null && items.Any()) {
postedCityIDs = items;
postedItems.ItemIDs = items;
}
if (postedItems.ItemIDs != null && postedItems.ItemIDs.Any()) {
postedItemIDs = postedIems.ItemIDs;
model.WasPosted = true;
}
if (postedItemIDs.Any())
selectedItems = ItemRepository.GetAll()
.Where(x => postedItemIDs.Any(s => x.Id.ToString().Equals(s))).ToList();
model.AvailableItems = ItemRepository.GetAll();
model.SelectedItems = selectedItems;
model.PostedItems = postedItems;
return View(model);
}
How might I reuse it in different Actions in my controller without having to copy/paste. I tried doing a private method with the code. But I am stuck on:
Either calling it wrong within an action method : private void Item (Item item) {//copied code from above} then calling Item(item); in the action; or
It has something to do with the (string[] items, PostedItems postedItems) that I am doing wrong; or
Something entirely different that I am not doing right.
Any examples would be much appreciated.
EDIT: The code above works with a CheckBoxList. It's one particular CheckBoxList. But I want to be able to use it in other views without having to copy/paste the code to other ActionResults. Just calling the ActionResult won't work, because I plan on doing other things. In particular, I have code for wizards in each ActionResult, such as:
if ((nextButton != null) && ModelState.IsValid)
return RedirectToAction("EMailConfirm");
return View("EMail/BasicDetails", myData);
which are returning specific views, so call to just the ActionResult won't work, unless I am missing something.
return View(model); tries to find a view for the original action.
Specify return View("ModelBased", model); to always render the view named "ModelBased"
public void SomeAction(string[] items, PostedItems postedItems)
{
// Modify the data as your like
return ModelBased(string[] items, PostedItems postedItems);
}
public void SomeOtherAction(string[] items, PostedItems postedItems)
{
// Modify the data as your like
return ModelBased(string[] items, PostedItems postedItems);
}
private ActionResult ModelBased(string[] items, PostedItems postedItems) {
var model = new ItemsViewModel();
var selectedItems = new List<Item>();
var postedItemIDs = new string[0];
if (postedItems == null) postedItems = new PostedItems();
if (items!= null && items.Any()) {
postedCityIDs = items;
postedItems.ItemIDs = items;
}
if (postedItems.ItemIDs != null && postedItems.ItemIDs.Any()) {
postedItemIDs = postedIems.ItemIDs;
model.WasPosted = true;
}
if (postedItemIDs.Any())
selectedItems = ItemRepository.GetAll()
.Where(x => postedItemIDs.Any(s => x.Id.ToString().Equals(s))).ToList();
model.AvailableItems = ItemRepository.GetAll();
model.SelectedItems = selectedItems;
model.PostedItems = postedItems;
return View(model);
}
Your example is unclear, however, I would normally move common functionality into a seperate method and mark it with [NonAction] attribute. E.g.
[NonAction]
protected UserInfo GetUserInfo(string username)
{
// Return relevant data
}
I would then call GetUserInfo in your action method.
Edit:
You need to look into partial views. You can think of a partial view as a control that you can re-use on multiple pages. For example, I can put a login control in a partial view and renged it on multiple pages. This will promote code re-usability.
I can't give you the example as I haven't done this for a while, but you'd have to do the following:
Instead of return View(); you'll have to return PartialView("_NameOfYourPartialView", viewModel);
Modify your view, so it's no longer a view, but a partial view.
You'll need to do a bit of reading and try it out for yourself.
Good luck
You can call this action from another action that returns ActionResult.
public ActionResult OtherAction()
{
return ModelBased(items, postedItems);
}
Also, why private void? Which part do you actually want to reuse? If it takes an Item and returns ItemsViewModel, it should be private ItemsViewModel - depends on the part you want to reuse. void doesn't return anything.
Related
I have a page with many items on it. Each one has a button, that is supposed to take user to another jsp with another layout for detailed information about the current item. Can I even do this with ResponseEntity, as it doesn't redirect anywhere? Or may be there's some better way to do it and send my Object to the page? I tried "ResponseEntity.created(location).body(object)" but it doesn't do the job, I stay on the same page. May be I'm just using it wrong?
My method:
#RequestMapping(value = {"/details+{id}"}, method = RequestMethod.GET)
public ResponseEntity<Item> details(#PathVariable("id") int id) {
Item item = itemService.findById(id);
if(item == null){
return new ResponseEntity<Item>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<Item>(item, HttpStatus.OK);
}
Have a look at ModelAndView. It's purpose is to return a view with attached model to it. So for each value of the id, you can decide a pair of view and model to return.
#RequestMapping(value = {"/details+{id}"}, method = RequestMethod.GET)
public ModelAndView details(#PathVariable("id") int id) {
String viewToUse;
Map<String, Item> modelToUse;
if(id == ...) {
viewToUse = ...
modelToUse = ...
} else if (id == ...) {
viewToUse = ...
modelToUse = ...
} else if (id == ...) {
viewToUse = ...
modelToUse = ...
}
return new ModelAndView(viewToUse, modelToUse);
}
How can i cache my items and values for dropdown list in MVC?
Is there a way to do so?
I am doing that in controller.
Sample code is.......
public ActionResult Index()
{
RegionTasks regionTasks = new RegionTasks();
ViewBag.Region = GetRegions();}
My controller has function as below.
[OutputCache(Duration = 10, Location = System.Web.UI.OutputCacheLocation.Server)]
private IEnumerable<SelectListItem> GetRegions()
{
RegionTasks regionTasks = new RegionTasks();
return regionTasks.GetRegions();
}
I have tested and it not caches the item for region.
How can i do that?
The OutputCache attribute is used on controller actions to cache the resulting output. It has strictly no effect on other methods.
If you want to cache custom objects you could use the HttpContext.Cache:
private IEnumerable<SelectListItem> GetRegions()
{
var regionTasks = HttpContext.Cache["regions"] as IEnumerable<SelectListItem>;
if (regionTasks == null)
{
// nothing in the cache => we perform some expensive query to
// fetch the result
regionTasks = new RegionTasks().GetRegions();
// and we cache it so that the next time we don't need to perform
// the query
HttpContext.Cache["regions"] = regionTasks;
}
return regionTasks;
}
The regionTasks are now cached under the regions key and accessible from anywhere in your ASP.NET application which has access to the HttpContext.Cache.
Darin is also right, how ever i have done following code to store on server for X minutes.
private IEnumerable<SelectListItem> GetGlobalUrlRegion(string profileName)
{
string cacheKey = "cacheUrlRegion";
RegionTasks regionTasks = RegionTasks.CreateRegionTasks();
IEnumerable<SelectListItem> regionUrlList = HttpContext.Cache[cacheKey] as IEnumerable<SelectListItem>;
if (regionUrlList == null)
{
var regionObject = regionTasks.GetRegions(profileName);
var cTime = DateTime.Now.AddMinutes(int.Parse(System.Configuration.ConfigurationSettings.AppSettings["GlobalCacheDurationInMin"].ToString()));
var cExp = System.Web.Caching.Cache.NoSlidingExpiration;
var cPri = System.Web.Caching.CacheItemPriority.Normal;
regionUrlList = regionObject;
HttpContext.Cache.Insert(cacheKey, regionObject, null, cTime, cExp, cPri, null);
}
return regionUrlList;
}
I want to validate my View Model in class-Level .
I am using a actionFilter. How do I use a data annotation?
and how to inject the Access database?
A validation that would happen if the customer says it is already our customer or not.
I used action filter but I think it must have a way to use a DataAnnotation
Commented the code follows:
public class DadosAssinaturaFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var model = filterContext.ActionParameters.Values.FirstOrDefault(x => x.GetType() == typeof(DadosAssinatura)) as DadosAssinatura;
var modelState = filterContext.Controller.ViewData.ModelState;
if (model != null)
{
var jaSouCliente = modelState.FirstOrDefault(x => x.Key == "JaSouCliente");
if (jaSouCliente.Key != null) // select "Is Clilent" radiobutton ?
if (jaSouCliente.Value.Errors.Count > 0) // if so remove the errors of the registration data
{
modelState.RemoveKeysStartsWith("DadosCliente.");
modelState.RemoveKeysStartsWith("DadosAcesso.");
}
else if (model.JaSouCliente != null && model.JaSouCliente.Value) // else, click in "Is Client"
{
modelState.RemoveKeysStartsWith("DadosCliente."); //remove
modelState.Remove("DadosAcesso.ConfirmaSenha"); //how injec UnitOfWor/Repository? AutoFac?
if (unitOfWork.Client.GetClientByUser(model.DadosAcesso.Usuario, model.DadosAcesso.Senha) == null)//user and Password
modelState.AddModelError("DadosAcesso.Usuario", "Usuario Nao Encontrado");
}
else if (model.DadosCliente.PessoaFisica) // is a company our people?
{
modelState.Remove("DadosCliente.RazaoSocial"); // remove validate for company name
modelState.Remove("DadosCliente.Cnpj"); //the brazilian document of company
}
else modelState.Remove("DadosCliente.Cpf"); //the brazilian document of people
}
base.OnActionExecuting(filterContext);
}
}
public static class ModelStateErros
{
public static void RemoveKeysStartsWith(this ModelStateDictionary modelStateDictionary, string startsWith)
{
var keys = modelStateDictionary.Keys.Where(key => key.StartsWith(startsWith)).ToList();
foreach (var variable in keys)
{
modelStateDictionary.Remove(variable);
}
}
}
sorry my English
Simply implement IValidateableObject in your ViewModel class (or create another partial class) and avoid the filter completely, and keep your validation logic with your ViewModel.
How do I use IValidatableObject?
I'm currently working on a MVC.NET 3 application; I recently attended a course by "Uncle Bob" Martin which has inspired me (shamed me?) into taking a hard look at my current development practice, particularly my refactoring habits.
So: a number of my routes conform to:
{controller}/{action}/{type}
Where type typically determines the type of ActionResult to be returned, e.g:
public class ExportController
{
public ActionResult Generate(String type, String parameters)
{
switch (type)
{
case "csv":
//do something
case "html":
//do something else
case "json":
//do yet another thing
}
}
}
Has anyone successfully applied the "replace switch with polymorhism" refactoring to code like this? Is this even a good idea? Would be great to hear your experiences with this kind of refactoring.
Thanks in advance!
The way I am looking at it, this controller action is screaming for a custom action result:
public class MyActionResult : ActionResult
{
public object Model { get; private set; }
public MyActionResult(object model)
{
if (model == null)
{
throw new ArgumentNullException("Haven't you heard of view models???");
}
Model = model;
}
public override void ExecuteResult(ControllerContext context)
{
// TODO: You could also use the context.HttpContext.Request.ContentType
// instead of this type route parameter
var typeValue = context.Controller.ValueProvider.GetValue("type");
var type = typeValue != null ? typeValue.AttemptedValue : null;
if (type == null)
{
throw new ArgumentNullException("Please specify a type");
}
var response = context.HttpContext.Response;
if (string.Equals("json", type, StringComparison.OrdinalIgnoreCase))
{
var serializer = new JavaScriptSerializer();
response.ContentType = "text/json";
response.Write(serializer.Serialize(Model));
}
else if (string.Equals("xml", type, StringComparison.OrdinalIgnoreCase))
{
var serializer = new XmlSerializer(Model.GetType());
response.ContentType = "text/xml";
serializer.Serialize(response.Output, Model);
}
else if (string.Equals("csv", type, StringComparison.OrdinalIgnoreCase))
{
// TODO:
}
else
{
throw new NotImplementedException(
string.Format(
"Sorry but \"{0}\" is not a supported. Try again later",
type
)
);
}
}
}
and then:
public ActionResult Generate(string parameters)
{
MyViewModel model = _repository.GetMeTheModel(parameters);
return new MyActionResult(model);
}
A controller should not care about how to serialize the data. That's not his responsibility. A controller shouldn't be doing any plumbing like this. He should focus on fetching domain models, mapping them to view models and passing those view models to view results.
If you wanted to "replace switch with polymorphism" in this case, you could create three overloaded Generate() ActionResult methods. Using custom model binding, make the Type parameter a strongly-typed enum called DataFormat (or whatever.) Then you'd have:
public ActionResult Generate(DataFormat.CSV, String parameters)
{
}
public ActionResult Generate(DataFormat.HTML, String parameters)
{
}
public ActionResult Generate(DataFormat.JSON, String parameters)
{
}
Once you get to this point, you can refactor further to get the repetition out of your Controller.
Let's take this n-tier deep structure for example:
public class SomeItem
{
public Guid ID { get;set; }
public string Name { get; set; }
public bool HasChildren { get;set; }
public IEnumerable<SomeItem> Children { get; set; }
}
If I am looking to get a particular Item by ID (anywhere in the structure) is there some LINQ goodness I can use to easily get it in a single statement or do I have to use some recursive function as below:
private SomeItem GetSomeItem(IEnumerable<SomeItem> items, Guid ID)
{
foreach (var item in items)
{
if (item.ID == ID)
{
return item;
}
else if (item.HasChildren)
{
return GetSomeItem(item.Children, ID);
}
}
return null;
}
LINQ doesn't really "do" recursion nicely. Your solution seems appropriate - although I'm not sure HasChildren is really required... why not just use an empty list for an item with no children?
An alternative is to write a DescendantsAndSelf method which will return all of the descendants (including the item itself), something like this;
// Warning: potentially expensive!
public IEnumerable<SomeItem> DescendantsAndSelf()
{
yield return this;
foreach (var item in Children.SelectMany(x => x.DescendantsAndSelf()))
{
yield return item;
}
}
However, if the tree is deep that ends up being very inefficient because each item needs to "pass through" all the iterators of its ancestry. Wes Dyer has blogged about this, showing a more efficient implementation.
Anyway, if you have a method like this (however it's implemented) you can just use a normal "where" clause to find an item (or First/FirstOrDefault etc).
Here's one without recursion. This avoids the cost of passing through several layers of iterators, so I think it's about as efficient as they come.
public static IEnumerable<T> IterateTree<T>(this T root, Func<T, IEnumerable<T>> childrenF)
{
var q = new List<T>() { root };
while (q.Any())
{
var c = q[0];
q.RemoveAt(0);
q.AddRange(childrenF(c) ?? Enumerable.Empty<T>());
yield return c;
}
}
Invoke like so:
var subtree = root.IterateTree(x => x. Children).ToList();
hope this helps
public static IEnumerable<Control> GetAllChildControls(this Control parent)
{
foreach (Control child in parent.Controls)
{
yield return child;
if (child.HasChildren)
{
foreach (Control grandChild in child.GetAllChildControls())
yield return grandChild;
}
}
}
It is important to remember you don't need to do everything with LINQ, or default to recursion. There are interesting options when you use data structures. The following is a simple flattening function in case anyone is interested.
public static IEnumerable<SomeItem> Flatten(IEnumerable<SomeItem> items)
{
if (items == null || items.Count() == 0) return new List<SomeItem>();
var result = new List<SomeItem>();
var q = new Queue<SomeItem>(collection: items);
while (q.Count > 0)
{
var item = q.Dequeue();
result.Add(item);
if (item?.Children?.Count() > 0)
foreach (var child in item.Children)
q.Enqueue(child);
}
return result;
}
While there are extension methods that enable recursion in LINQ (and probably look like your function), none are provided out of the box.
Examples of these extension methods can be found here or here.
I'd say your function is fine.