New Umbraco Package – Media Link Checker

by AaronPowell 3. March 2010 15:52

Ever been trying to work out if a media item is used within your site? And if it is, where are all the places that it’s being used?

Here at TheFARM we had this problem and we set about solving it, and once solved we thought it’d be a great idea to share it with the community.
To this extent we’ve put together a handy little Umbraco Data Type which you can attach to any media type and then find out its usages.

It’ll look up based on:

  • Media Pickers (or DataTypes which store the media node ID)
  • Usage within the WYSIWYG editor (based on the file-system path)

Version 1.0 of the package (released today) supports Umbraco 4.0.x.

When Umbraco 4.1 is released we’ll be producing an updated version of the Data Type which will have some nifty new features which it can leverages from the new 4.1 features.

So what can you do with it?

  • When the Data Type locates an instance of the media item attached to a media picker you have the option to directly disconnect it!
    • This will modify the document, but leave it in a unpublished state, so you can go review your change before putting it live
  • When the Data Type locates an instance of the media item in a WYSIWYG field it will allow you to view the full path to the document in the content tree, so you can navigate there and address it

But really, a picture is worth a thousand words, so since there’s a lot of pictures in a video I guess this is worth a hell of a lot - http://www.screencast.com/t/ZDcxNjQ2ND

So now that you’re excited why not jump over to the project section of Our.Umbraco and grab a copy!

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Umbraco

Using .NET 3.5 features without installing .NET 3.5

by AaronPowell 23. February 2010 03:48

I recently worked on a project for a client which the deployment environment was only to have .NET 2.0 installed. This is a rarity these days, after all .NET 3.5 was released in November 2007 (http://en.wikipedia.org/wiki/.NET_Framework#.NET_Framework_3.5), so having chosen to not install it is a bit of an effort.

works-on-my-machine-starburst

But fine, what ever floats your boat I guess, but there is a problem, pretty much all the libraries which we’ve developed at The FARM are .NET 3.5 libraries, generally because they use features like LINQ or Extension Methods.
Damn, that’s going to be a pain in the ass, I’d either have to forgo our libraries, or make custom versions of them.

Or, I could be a bit ninja-esq and make a .NET 2.0 website, which has .NET 3.5 assemblies in it, now that sounds like fun.

But let’s step back a bit, what is .NET 3.5 in relation to .NET 2.0?
Well really .NET 3.5 is a super-set of the .NET 2.0 framework (well, technically a super-set of .NET 3.0, which itself was a super-set of .NET 2.0, but you get the idea). But why is that? Well .NET 3.5 is built on top of the same CLR (Common Language Runtime) which powers .NET 2.0.

Since most people associate .NET 3.5 with Visual Studio 2008 so I’ll talk about them in a combined manner.

In addition to the .NET 3.5 release we also received the C# 3 compiler. This nifty little bugger brought along the translation of => to being either a Func, Action or Predicate, and it also introduced the var keyword.
But the fun thing is that because C# 3 is targeted at the 2.0 CLR all the compiled IL is perfectly valid for .NET 2.0! This is actually how (and why) multi-targeting in Visual Studio 2008 works (well, how and why at a very high level :P) and why you can use var, lambda, etc in a .NET 2.0 project provided it’s made in Visual Studio 2008.

But back to our original topic, how do I use .NET 3.5 without installing it on the server? (Note – you’ll still need it installed on the dev environment)

Putting the pieces together

So we’ll create a new ASP.NET web application, still choosing .NET 3.5 as the framework version and do everything as per normal. The tricky part is when we come to wanting to deploy the site onto our target environment, which doesn’t have .NET 3.5 installed.
Before we can do this there is one last thing we need to change within Visual Studio, you need to change the way the Visual Studio handles the referenced assemblies. Framework assemblies (well, any assembly which is in the Global Assembly Cache (GAC)) are set to Copy Local=False, you need to change this to True, so right-click the assembly and go to Properties:

properties

Now what will happen is that when the compile is done the assembly/ assemblies will be copied into the /bin folder of your site, meaning now they are loaded into memory, but not loaded there from the GAC!

You can then take all the files and deploy them to the target environment, with the .NET 3.5 assemblies in the /bin folder, but not installed.

Really a great trick with Shared Hosting ;)

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

.Net | Hosting | Umbraco

More on Umbraco, TinyMCE and Flash

by AaronPowell 3. February 2010 10:05

In a previous post Shannon explained how to customise TinyMCE for what HTML elements Flash actually supports, and he finished off the post with showing how to cleanup line breaks.

To do this he used an XSLT function called normalize-space, which is great if you’re using XSLT!

I was writing a service today which was using LINQ to XML to generate the XML for Flash, but that posed a problem, how do you deal with Flash wanting to do hard breaks on new line constants?

Easy, string.Replace to the rescue!

Here’s a handy little extension method you can drop into your code libraries:

public static string NormalizeSpace(this string s) {
	if(s == null) throw new ArgumentNullException("s");
	return s.Replace("\r\n", string.Empty)
		.Replace("\r", string.Empty)
		.Replace("\n", string.Empty);
}

Nice and easy (and unsurprisingly logical!).

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , , ,

.Net | Flash | Umbraco

Don’t deploy your .svn folders!

by aaronpowell 18. January 2010 04:37

Last year there was an article posted on TechChrunch about the problem of deploying your .snv folder live, it’s a really great way to give everyone your websites source code!

Recently though while tweaking our 2 Click ASP.NET Web Application Deployment with MSBuild to include a static folder consisting of the umbraco/ umbraco_client folders (which we leave excluded from the project to ensure performance of Visual Studio) I noticed that we were including the .svn folders!

We’re generating an ItemGroup like this:

<ItemGroup>
  <Umbraco Include="$(LocationWorkingWeb)\umbraco\**\*.*"/>
  <UmbracoClient Include="$(LocationWorkingWeb)\umbraco_client\**\*.*"/>
</ItemGroup>

Which recursively adds the files from those folder, including .svn.

Balls!

Sure it’s not really a problem, we’ve got no source code stored in those folders (and anyone who is putting their own source in umbraco or umbraco_client is asking for trouble), but by including them you’re pretty much doubling the size of the folder structure too!

Luckily it’s quite easy to solve. MSBuild has a build-in Exclude attribute, so you just need to change it to look like this:

<ItemGroup>
  <Umbraco Include="$(LocationWorkingWeb)\umbraco\**\*.*" Exclude="$(LocationWorkingWeb)\umbraco\**\.svn\**\*" />
  <UmbracoClient Include="$(LocationWorkingWeb)\umbraco_client\**\*.*" Exclude="$(LocationWorkingWeb)\umbraco_client\**\.svn\**\*" />
</ItemGroup>

It looks a bit weird, you’ve got to recursively exclude the recursive contents of the .svn folder :P

It’s all about making sure you only deploy what you should have on a production server, and it goes in hand with remembering that PDB != Product Deployable Bits.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

.Net | Umbraco

Changing the hostname of a SharePoint site

by aaronpowell 8. January 2010 10:44

We’ve recently set up a SharePoint server here at TheFARM which will run parts of our intranet and be our document management system.

So it got installed, but the problem was that it was installed onto a machine called mars. I made the obligatory jokes about ‘life on mars’ (admittedly I may have made the joke a few to many times :P) but at the end of last year we ran a competition to name the new intranet.

There were some fun names like SkyNet, and Randall, but ultimately the winning entry was TheBarn, which is very aptly farm-based.
But we had a problem, we don’t want to rename the server from mars (plus I’ve done that on SharePoint before, baaaaaaaaaaaaaaaaaaaaaaaaaaaaad idea), so how do you get SharePoint to accept http://thebarn when that’s not the machine name?

Unlike standard standard sites in IIS just adding a host header isn’t going to work, SharePoint will redirect you to the one it knows about, so although we were coming in via http://thebarn we’d end up at http://mars.

Hmmm…

Luckily it is actually very easy to do with SharePoint. SharePoint has the ability to Extend a web application:

image

So you navigate here, choose the Extend an existing Web application, select your site and enter the hostname (and set the port back to 80):

image

Now you’ll have a SharePoint site which listens on your new host header. You can go and delete the old one if you want (Remove SharePoint from IIS Web site) and then you’re done.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

SharePoint

Excluding pages with URL Rewriting

by aaronpowell 23. December 2009 10:17

Today we had a mini site which the promotion had completed, but the site was to stay live with a few of the content pages staying live for people who come to view it.

Because the site was only a few pages there isn’t a CMS back-end, it’s just flat ASPX pages are stored in a single folder.
The problem is, that the pages which need to be available are also in the folder which the pages which are not longer accessible are.

There is a few ways in which I can take down the pages which people aren’t meant to access, firstly I could just delete them. That’d work, but that’d be a 404 error which isn’t really a good user experience (I could set the 404 redirect to be the home page, but the browser would still receive a 404 HTTP status).

So the next option is URL Redirection, lets crack out our UrlRewriting.config file and set up a rule. We’ll start with a rule like this:

<add name="RedirAllIndexedPagesToDefault" virtualURL="^(.*)/pages/(.*)" destinationUrl="$1/default.aspx" redirectMode="Permanent" redirect="Domain" ignoreCase="true" />

There, that’ll handle everything in our pages folder, but it doesn’t help with the pages which I want accessible. Say the pages I want accessible are TermsAndConditions.aspx and PrivacyPolicy.aspx, but I don’t want Entry.aspx accessible.
It’s just a matter of modifying my virtualURL to be a bit stricter, so now it looks like this:

<add name="RedirAllIndexedPagesToDefault" virtualURL="^(.*)/pages/(?![TP.*])(.*)" destinationUrl="$1/default.aspx" redirectMode="Permanent" redirect="Domain" ignoreCase="true" />

Now you’ll not be able to get to Entry.aspx, but the other two pages will still work just fine.

Sure it’s not great if you’ve got lots of pages (and if you did have lots of pages I’d hope there was a CMS to deal with it), but it’s good for mini sites which you want some pages but not others in a folder accessible.

Currently rated 4.0 by 1 people

  • Currently 4/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

.Net

Can’t save changes to an Umbraco user

by aaronpowell 2. December 2009 11:36

The other day Shannon was griping about a problem he’d come across when editing a user in Umbraco (a back-office account). When ever he clicked the Save button nothing happened, unless he’d gone and edited the Username field as well.

Today I noticed the same problem, I was trying to edit a user (I was changing the applications they had access to), I’d click save and nothing would happen! There was no error, there was no message from Umbraco.
It was as if the Save button was disabled.

And then it hit me, I was using Firefox and I had saved the username/ password combination when I logged into Umbraco.
That makes sense, the error seemed like some kind of client validation had been failing, but there was nothing obvious to me.
Then I realised that the password fields for the user details were hidden, so I clicked the Change Password link and look there, I have an error saying that my passwords don’t match.

Because I’d saved the username/ password in Firefox it was kind enough to auto-complete one of the password fields (using what ever algorithm Firefox does to detect password fields), but then there was nothing in the confirm field, causing the compare validator to fail.

So I cleared the field and clicked save, and all is well.

 

But why did it work when we changed the username? Well then Firefox detected that there was no password for that user, so it cleared the field, and the compare validator passed (it don’t compare empty fields).

 

Lesson learnt – be careful when you’re storing passwords :P

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

Umbraco | Fail

An update with the multiple environment Umbraco data sync

by AaronPowell 9. October 2009 10:08

A month ago I wrote a post on how to sync data across multiple Umbraco environments, and I thought that I would do an update post on how it is tracking.

Well the short answer is that it is going really quite well. We’ve quite successfully migrated content from the clients content entry environment into their production environment with next to no issues.

The only problem we’ve had is when the same document has been edited on both environments. When this happens you need to work out which is the correct version to maintain, and if it’s published you need to ensure that you’re using the correct version in the cmsContentXml table.

Only thing that needs to be kept in mind is that once the data is merged you need to ensure that the primary key seed is still higher on the content entry environment so that when you next do the synchronization the ID’s don’t clash between environments.

 

The only other thing that you need to be mindful of is Media, you will need to ensure there is something synchronizing that, either a tool such as SyncToy or you manually manage the Media folder synchronization.

Currently rated 3.0 by 1 people

  • Currently 3/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

Umbraco

2 Click ASP.NET Web Application Deployment with MSBuild

by AaronPowell 2. October 2009 06:12

A couple of months ago Shannon wrote a post on how we do 2 Click Deployments at The Farm (if you haven’t read this post I suggest you read it first as I make the assumption it’s been read and skip over a few sections that it covers). I’ve been working with him to try and improve our process of deployment, and one of those tasks has been moving away from using NAnt as the build runner to using MSBuild.

Why the shift? Well MSBuild has actually come a long way recently and despite common belief it’s not just for building .NET applications. In fact it can be used the same way that NAnt can be used, to execute arbitrary operations.
As mentioned in the original post we’ve used an internal tool for modifying the web.config (and other configuration files) but it had a bit a limitation as it meant that the files were always full of settings for all environments. It also wasn’t designed to update a single property of an XML node, you’d need to replicate the whole XML structure, even if it was just 1 attribute changing.

So I suggested that we migrate to a XSLT based config management, which is what I’d used previously. A former colleague of mine has a good post on how to set that up, it can be found here.

Moving to MSBuild also brought in another advantage, it would simplify our deployment process. Currently we’re using CruiseControl.NET to execute a NAnt script which in turn executed MSBuild.

Another goal was to allow us to deploy from the branch in source control, rather than from the trunk, this way you can deploy different branches if you want to test different functionality based on a branch without overriding the trunk. So we’ve updated our source control structure to look like this:

  • trunk
  • branch
    • v1.1
  • build

The new addition is the build folder, this folder will generally only hold a single file, and that’s the master build information, which we’ll call cc.build. This build file is the one that CruiseControl.NET will look for and check out. It then contains the instructions to which branch to check out, and then handle the rest of the execution of the build. A sample looks like this:

<Project ToolsVersion="3.5" DefaultTargets="CheckoutAndBuild" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>

  <PropertyGroup>
    <BranchVersion>v1.1_net</BranchVersion>
    <BuildType>_net</BuildType>
    <BranchFolder>$(BaseFolder)\$(BuildType)</BranchFolder>
    <RepoPath>file:///\svn/Client/branches/$(BranchVersion)</RepoPath>
  </PropertyGroup>

  <Target Name="CheckoutAndBuild">
    <RemoveDir Directories="$(BranchFolder)" />

    <SvnCheckout RepositoryPath="$(RepoPath)"
					LocalPath="$(BranchFolder)"
					ToolPath="C:\Program Files\CollabNet Subversion">
      <Output TaskParameter="Revision" PropertyName="Revision" />
    </SvnCheckout>

    <Message Text="Revision: $(Revision)"/>

    <Time Format="yyyyMMddtt">
      <Output TaskParameter="FormattedTime" PropertyName="buildDate" />
    </Time>

    <MSBuild Projects="$(BranchFolder)\cc.msbuild"
             Properties="CCNetWorkingDirectory=$(BaseFolder);BuildDate=$(BuildDate);DeployType=Dev;DeployLocation=$(BaseFolder)..\Deployment\Dev;BuildConfiguration=Debug;ShowMessages=True;IncludeSymbols=True" />
  </Target>

</Project>

So lets have a look at the break down of this file. First off, we’re using the MSBuild Community Tasks for tasks such as the SVN checkout, and a few other aspects which we’ll cover later.

When we call this MSBuild file from CruiseControl.NET and pass in a parameter, BaseFolder, which is the Working folder (see the Folder Structure section of the original post).
There’s some parameters combined so we get a path to where we’ll be checking out the branch, etc.

First thing is to remove any existing checkout folder, and then have SVN checkout the latest copy of the branch.
As Shannon mentioned in the original post we timestamp each sip package, so for this we’re using the Community Task date formatter to generate our timestamp.

Then it gets a bit tricky, because this file is a dumb file and doesn’t know what’s going to be done by the branch deployment file so we need to have a way in which we can actually do a build. This is done by using the MSBuild task which allows you to execute any specified MSBuild file(s). This is done by passing the file to execute into the Projects attribute. Since we’re going to execute the one within the branch we tell it to look there.
Then it’s just a matter of passing any properties you require into the MSBuild file, here we’ve got a bunch of different parameters which the target file can consume.

The MSBuild task is then replicated as many times for each different environment/ location which you want to build for.

 

Each branch maintains its own msbuild file, which is also called cc.msbuild so that we can keep the naming consistent across all locations. This file is the one which is responsible for:

  • Compiling the project
  • Modifying the config files
  • Zipping the release

But this can easily be expanded upon, depending how the branch needs to be handled, there’s nothing that says it couldn’t also do the copying to the appropriate server, etc. Lets take a look inside this file.

<Project ToolsVersion="3.5" DefaultTargets="ZipFiles" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>

  <PropertyGroup>
    <LocationWorkingWeb>$(CCNetWorkingDirectory)\_net\Client.Web</LocationWorkingWeb>
    <ProjectWeb>$(LocationWorkingWeb)\Client.Web.csproj</ProjectWeb>
    <BuildFolder>$(DeployLocation)\$(BuildDate)\</BuildFolder>
  </PropertyGroup>

  <Target Name="ZipFiles" DependsOnTargets="FormatFiles">
    <ItemGroup>
      <ZipFiles Include="$(BuildFolder)**\*.*" Condition="$(IncludeSymbols) == 'True'" />
      <ZipFiles Include="$(BuildFolder)**\*.*" Exclude="$(BuildFolder)**\*.pdb" Condition="$(IncludeSymbols) == 'False'" />
    </ItemGroup>

    <Zip Files="@(ZipFiles)"
         ZipFileName="$(DeployLocation)\$(BuildDate).zip"
         WorkingDirectory="$(BuildFolder)"/>

    <RemoveDir Directories="$(BuildFolder)" />
  </Target>

  <Target Name="FormatFiles" DependsOnTargets="BuildWebProject">
    <MSBuild Projects="transformers.msbuild"
             Properties="XslFile=$(CCNetWorkingDirectory)\_net\CruiseControl\Web.config.xslt;OutputFile=$(BuildFolder)Web.config;InputFile=$(LocationWorkingWeb)\Web.config;Environment=$(DeployType)" />
  </Target>

  <Target Name="BuildWebProject">
    <MSBuild Projects="$(ProjectWeb)"
             Properties="Configuration=$(BuildConfiguration);OutDir=$(BuildFolder)bin\;WebProjectOutputDir=$(BuildFolder)"
             Targets="Clean;Build;ResolveReferences;_CopyWebApplication"/>
  </Target>
</Project>

This file, again uses the MSBuild Community Tasks, but its also a whole lot smarter about what’s happening for this particular build. I’ve specified that the DefaultTargets of the file is the ZipFiles which is really the final task which I want to execute.
One really nice thing about MSBuild is that you can specify a DependsOnTargets attribute for a target, which states that the particular target(s) must have executed before the called one will execute. So by looking at how the dependency is configured the project will have been compiled and the files formatted will both have been done. Makes it so much simpler to execute something, rather than having a ‘runner’ target which is just responsible for calling a bunch of targets in order we can just have them done in order of dependency.

So we’ve got a BuildWebProject target which will again call an external MSBuild file, in this case our csproj file, passing in some of the information which was given to us from the ‘master’ file, in a manner in which Visual Studio itself would have done.

Next we format the files. This goes off to an external MSBuild file, so we can use the same operations for every single file we want to format. The contents of transformers.msbuild is as follows:

<!-- ROBOTS IN DISGUISE -->
<Project ToolsVersion="3.5" DefaultTargets="Xslt" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
  <ItemGroup>
    <XslFileWithParams Include="$(XslFile)">
      <environment>$(Environment)</environment>
    </XslFileWithParams>
  </ItemGroup>
  <Target Name="Xslt">
    <Xslt RootTag="" Inputs="$(InputFile)" Output="$(OutputFile)" Xsl="@(XslFileWithParams)" />
  </Target>
</Project>

This is a basic task file which adds a parameter (named envrionments) to our XSLT and then uses the MSBuild Community Tasks to do the transformation.
Note – this is a slight deviation from Alistair’s blog post, we don’t have a different XSLT per machine, we have a single XSLT which we use the parameter to determine what to do. This makes it easier to see all the options we want to change per environment.

Lastly the dependencies hierarchy throws us back to the ZipFiles target, where we gather all the files together that we want (and there’s an option to exclude the pdb files, after all PDB != Product Deployable Bits ;) ), call the Zip task (make sure you specify a working folder otherwise you’ll end up with a crazy hierarchy in the Zip!) and then we delete the folder which we built to.

 

Conclusion

MSBuild is a good tool which can be used for doing operations other than just compiling a .NET project, as you can see here we’re using it to pull files down out of SVN or transform a file via XSLT. This doesn’t have anything to do with compilation, or .NET for that matter.

It make take a bit of work to get set up, there was quit a bit of frustration vented during this process but now that it’s working there’s no looking back.

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , , , , , ,

.Net | Hosting | Umbraco

How to sync data across multiple Umbraco environments

by AaronPowell 9. September 2009 10:14

Recently we had a client who wanted to be able to preview content before they were publishing it live with Umbraco. Because we’d made some major modifications to the way the content was rendered we were unable to use the standard Umbraco preview since we weren’t using any of the <umbraco:item /> tags. So this posed a problem, how were we going to have the preview working?

We decided that we were going to have to have a content entry server, from here they could enter their content, publish it and view it, but how were we then to get it to the live site? The logical answer is that have a periodic push of the content entry servers database across to live, but that’s not ideal and it is a pretty nasty idea.

To add another level of complexity on top of the system the site has user generated content, this content would obviously be entered by site visitors and it would only be on the live site, not the staging site.

Hmm…

Then Shannon reminded me of a conversation which we had when we were in Denmark at CodeGarden 09, someone had suggested that you could use Red Gate’s SQL Data Compare to push the data across.
So I sat down with two copies of the database, and decided to work out what would be needed to achieve this. Essentially you need to reseed Umbraco so that when you do changes on one environment it can easily be spotted and resolved by Red Gate.

The following is the list of tables which you need to reseed:

  • cmsContent
  • cmsContentVersion
  • cmsDocument
  • cmsPropertyData
  • umbracoNode

Microsoft SQL Management Studio will be able to generate reseed scripts for these tables which will handle the data migration and everything.

With this set up it opens an interesting idea which we plan to trial, having every environment seeded differently from dev onwards and using Red Gate to migrate from one to the next.

Things to keep in mind:

If it’s likely that there will be content created/ edited on both environments make sure that you put the primary key seed to be greatly spaced. For this site we reseeded the content entry environment with a start identity of 500,000. This left a good sized buffer in the cmsPropertyData table which is the table that expands the most.

If you wanted to be able to sync across multiple environments you could do:

  • Dev – primary key index starting at 1
  • Content Entry – primary key index starting at 500,000
  • Live – primary key index starting at 1,000,000

If you want to sync more than just content (eg – Macros, Document Types, etc) you’ll have to reseed their appropriate tables too.

You still need to manually copy the Media folder across environments still, as those files aren’t stored in the database.

Currently rated 5.0 by 3 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

Umbraco

// Website built by The FARM